cppu版,老师讲的比较简单,所以很简略 重点复习实训
1.数据结构 数据:对客观事物的符号表示:图像、声音等 数据元素:数据段基本单位 数据项:构成数据元素的最小单位 数据对象:具有相同性质的数据元素的集合 数据结构:相互之间存在一种或多种特定关系的数据元素的集合 包含逻辑结构、存储结构、数据的运算 Data Structure=(D,S) 数据元素有限集、数据关系有限集 ,注意对应
题1.以下说法正确的是() A.数据项是数据的基本单位 B. 数据元素是数据的最小单位 C.数据结构是带结构的数据项的集合 D.数据元素可由若干数据项组成 答案:D 题2.数据的逻辑结构从形式上可用二元组(D,R)表示,其中R是()的有限集 A.算法 B.数据元素 C.数据项 D. 数据关系 答案:D
逻辑结构分为线性结构(线性表(栈、队列、串、数组)(一对多))、非线性结构(集合、树形结构(一对多)、图形结构或网状结构(多对多))
存储结构(物理结构):顺序存储(逻辑上和物理上位置一致)、链式存储(借助指示元素存储地址的指针) 、索引存储(索引表:索引项(关键字、地址))、散列存储(哈希存储)(关键字通过散列函数到存储地址)
2.算法 有穷性(有穷步骤和时间)、确定性、可行性、输入、输出
目标:正确性、可读性、健壮性、效率与低存储量需求
时间复杂度只保留频度之和的数量级
3.顺序表 ①线性表:具有相同数据类型的n个数据元素的有限序列 直接前驱/直接后继 特点
实训代码 题目:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 本关任务:对于顺序存储的线性表完成两个操作函数,分别实现顺序表中数据的插入、删除功能。 相关知识 实训中的类型定义及子函数的作用如下: (1)定义顺序表类型SqList; #define MAXSIZE 100 //顺序表可能达到的最大长度 typedef int ElemType; //假设顺序表中所有元素为int类型 typedef struct { ElemType *elem; //指向数据元素的基地址 int length; //顺序表的当前长度 } SqList; (2)子函数InitList(SqList &L),实现顺序表的初始化; (3)子函数CreateList(SqList &L),实现顺序表的创建,从键盘上依次输入整型数据元素,创建顺序表; (4)子函数ListInsert(SqList &L,int i ,ElemType e),实现顺序表中的插入运算; (5)子函数 ListDelete(SqList &L,int i),实现顺序表中的删除运算; (6)子函数PrintList(SqList L),实现顺序表的打印输出。
答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 #include <iostream> using namespace std;#define MAXSIZE 100 typedef int ElemType; typedef struct { ElemType *elem; int length; } SqList; void InitList (SqList &L) { L.elem = new ElemType[MAXSIZE]; if (!L.elem) exit (-2 ); L.length=0 ; } void CreateList (SqList &L) { int i,n; cin>>n; for (i=0 ;i<n;i++) { cin>>L.elem[i];} L.length=n; } int ListInsert (SqList &L,int i,ElemType e) { if (i<1 or i>L.length+1 )return 0 ; for (int j=L.length-1 ;j>=i-1 ;j--){ L.elem[j+1 ]=L.elem[j]; } L.elem[i-1 ]=e; ++L.length; return 1 ; } int ListDelete (SqList &L,int i) { if (i<1 or i>L.length+1 )return 0 ; for (int j=i-1 ;j<L.length;j++){ L.elem[j]=L.elem[j+1 ]; } --L.length; return 1 ; } void PrintList (SqList L) { int i; for (i=0 ;i<L.length;i++) cout<<L.elem[i]<<" " ; cout<<endl; } int main () { int r,k; SqList L; InitList (L); CreateList (L); r=ListInsert (L,5 ,6 ); if (r) PrintList (L); else cout<<"插入位置非法" <<endl; k=ListDelete (L,3 ); if (k) PrintList (L); else cout<<"删除位置非法" <<endl; return 0 ;}
题目2
1 本关任务:对于无序的顺序表,可能存在着一些值相同的“多余”数据元素(类型为整型),编写一个程序将“多余”的数据元素从顺序表中删除,使该表由一个“非纯表”(值相同的元素在表中可能有多个)变成一个“纯表”(值相同的元素在表中只保留第一个)。
答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <iostream> using namespace std;#define MAXSIZE 100 typedef int ElemType; typedef struct { ElemType *elem; int length; } SqList; void InitList (SqList &L) { L.elem = new ElemType[MAXSIZE]; if (!L.elem) exit (-2 ); L.length=0 ; } void CreateList (SqList &L) { int i,n; cin>>n; for (i=0 ;i<n;i++) { cin>>L.elem[i];} L.length=n; } void DeleteList (SqList &L) { int newlen=0 ; for (int i=0 ;i<L.length;i++) { bool ischongfu=false ; for (int j=0 ;j<newlen;j++) { if (L.elem[i]==L.elem[j]) { ischongfu=true ; break ; } } if (!ischongfu) { L.elem[newlen]=L.elem[i]; newlen++; } } L.length=newlen; } void PrintList (SqList L) { int i; for (i=0 ;i<L.length;i++) cout<<L.elem[i]<<" " ; cout<<endl; } int main () { SqList a; InitList (a); CreateList (a); DeleteList (a); PrintList (a); }
4.链表 链式存储的线性表 每个都有data(数据)和next(后继结点地址)
1 2 3 4 typedef struct LNode{ Elemtype data; struct LNode *next; }LNode,*Linklist; //前者强调结点,后者强调链表
结构有带头结点和不带头结点的 题1.若线性表采用链式存储,则表中各元素的存储地址 A. 必须是连续的 B. 部分地址是连续的 C. 一定是不连续的 D. 不一定是连续的 答案:D 题2.单链表中,增加一个头结点的目的是() A. 使单链表至少有一个结点 B. 标识表结点中首结点的位置 C. 方便运算的实现 D. 说明单链表是线性表的链式存储 答案:C
单链表的实现 实训1代码 题目:
1 2 3 4 5 6 7 8 9 10 11 12 本关任务:对于链式存储的线性表完成三个操作函数,分别实现单链表的创建、数据的插入、删除功能。 实训中的类型定义及子函数的作用如下: (1)定义单链表类型; typedef struct LNode{ ElemType data; //结点的数据域 struct LNode *next; //结点的指针域 }LNode,*LinkList; (2)子函数InitList(LinkList &L),实现单链表的初始化; (3)子函数CreateList(LinkList &L,int n),实现单链表的创建; (4)子函数ListInsert(LinkList &L,int i,ElemType e),实现单链表中的插入运算; (5)子函数 ListDelete(LinkList &L,int i,ElemType &e),实现单链表中的删除运算; (6)子函数PrintList(LinkList L),实现单链表的打印输出。
答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 #include <iostream> using namespace std;#define OK 1 #define ERROR 0 typedef int Status;typedef int ElemType; struct LNode { ElemType data; struct LNode *next; }; typedef struct LNode *LinkList; void InitList (LinkList &L) { L=new LNode; L->next=NULL ; } void CreateList (LinkList &L,int n) { L=new LNode; L->next=NULL ; for (int i=n;i>0 ;--i) { LinkList p=new LNode; cin>>p->data; p->next=L->next; L->next=p; } } int ListInsert (LinkList &L,int i,ElemType e) { LinkList p=L;int j=0 ; while (p and j<i-1 ){p=p->next;++j;} if (!p or j>i-1 )return 0 ; LinkList s=new LNode; s->data=e; s->next=p->next; p->next=s; return 1 ; } int ListDelete (LinkList &L,int i) { LinkList p=L;int j=0 ; while (p->next and j<i-1 ) { p=p->next; ++j; } if (!(p->next) or j>i-1 )return 0 ; LinkList q=p->next; p->next=q->next; LinkList data,e; delete q; return 1 ; } void PrintList (LinkList &L) { LinkList p=L->next; while (p) { cout<<p->data<<" " ; p=p->next; } cout<<endl; } int main () { int n,i,j,e,l; LinkList L; cin>>n; InitList (L); CreateList (L,n); PrintList (L); cin>>i; cin>>e; j=ListInsert (L,i,e); if (j) PrintList (L); else cout<<"插入位置非法" ; cin>>i; j=ListDelete (L,i); if (j) PrintList (L); else cout<<"删除位置非法" <<endl; return 0 ; }
实训题目2 题目:
1 2 3 4 本关任务:编写一个程序,实现带头结点的单链表的逆置。 为了完成本关任务,你需要掌握: 1.单链表中头插法的实现过程, 2.单链表中指针的修改。
答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 #include <iostream> using namespace std;#define OK 1 #define ERROR 0 typedef int Status;typedef int ElemType; struct LNode { ElemType data; struct LNode *next; }; typedef struct LNode *LinkList; void InitList (LinkList &L) { L=new LNode; L->next=NULL ; } void CreateList (LinkList &L,int n) { int i; LinkList p; L=new LNode; L->next=NULL ; for (i=n;i>0 ;--i) { p=new LNode; cin>>p->data; p->next=L->next; L->next=p; } } void ReverseList (LinkList &L) { LinkList p; p=L->next; L->next=NULL ; while (p) { LinkList q=p; p=p->next; q->next=L->next; L->next=q; } } void PrintList (LinkList &L) { LinkList p=L->next; while (p) { cout<<p->data<<" " ; p=p->next; } cout<<endl; } int main () { LinkList a; InitList (a); int n; cin>>n; CreateList (a,n); ReverseList (a); PrintList (a); }
按序号查找结点值O(n) 1 2 3 4 5 6 7 8 9 10 LNode *GetElem (LinkList L,int i) { int j=1 ; LNode *p = L-> next; if (i == 0 )return L; if (i<0 )return NULL ; while (p&&j<i){ p=p->next;j++; } return p; }
按值查找表结点O(n) 1 2 3 4 5 6 LNode *LocateElem (LinkList L, ElemType e) { LNode *p = L-> next; while (p != NULL &&p-> data != e) p=p->next; return p; }
题1.从一个具有n个结点的单链表中查找其值等于x的结点时,在查找成功的情况下,需比较()个元素结点 A. n/2 B. n C.(n+1)/2 D.(n-1)/2 答案:C 解析:求的是平均比较次数,所以x在每一个位置都有可能 (1+2+…+n)/n=(n+1)/2
插入结点 先按位查找p=GetElem(L,i-1) 然后让插入的结点的后继结点为原来第i-1个结点的后继结点(s->next=p->next) 最后让p的后继结点变成s 题2.在单链表中,已知p,q,s是指向结点的指针,且q是p的直接前驱,若在q和p之间插入s,则需执行() A. s->next = p->next; p-> next= s; B. q-> next =s; s-> next = p; C. p->next=s-> next; s-> next = p; D.p-> next=s; s-> next= q; 答案:B 解析:画个图体会一下
删除结点 先按位查找p=GetElem(L,i-1); 删除的结点q=p->next; 绕过区间p->next=q->next; 删除结点free(q);
循环单链表 L->next=L 循环链表中每一个元素都有后继 题4.在以下的叙述中,正确的是() A. 线性表的顺序存储结构优于链式存储结构 B. 线性表的顺序存储结构适用于频繁插入或删除数据元素的情况 C. 线性表的链式存储结构适用于频繁插入或删除数据元素的情况 D. 线性表的链式存储结构优于顺序存储结构 答案:C
5.栈和队列 栈:先进后出 队列:先进先出 循环队列 初始时:Q.front =Q.rear=0 队首指针进1:Q.front=(Q.front +1)%MaxSize 队尾指针进 1:Q.rear= (Q.rear+1)%MaxSize 队列长度:(Q.rear - Q.front + MaxSize )%MaxSize 队空条件:Q.front==Q.rear 牺牲一个单元来区分队空和队满,入队时少用一个队列单元 队满条件:(Q.rear+1)%MAXSIZE==Q.front 没啥,直接看实训
实训 题目1:顺序栈的基本操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 本关任务:对于顺序存储的字符栈完成三个操作函数,分别实现顺序栈的初始化,数据的入栈和出栈功能。 相关知识 实训中的类型定义及子函数的作用如下: (1)定义顺序栈类型SqStack; typedef struct { SElemType *base; SElemType *top; int stacksize; }SqStack; (2)子函数InitSqStack(SqStack &st),实现顺序栈的初始化; (3)子函数Push(SqStack &st, char e),实现顺序栈中的入栈运算; (4)子函数Pop(SqStack &st, char &e),实现顺序栈中的出栈运算; (5)主函数,在主函数中调用上述子函数,输出相应的结果。
答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <iostream> using namespace std;#define MAXSIZE 10 #define OVERFLOW -2 typedef char SElemType;typedef struct { SElemType *base; SElemType *top; int stacksize; }SqStack; void InitStack (SqStack &S) { S.base=new SElemType[MAXSIZE]; S.top=S.base; S.stacksize=MAXSIZE; } int Push (SqStack &S,SElemType e) { if (S.top-S.base==S.stacksize)return 1 ; *S.top=e; S.top++; return 0 ; } int Pop (SqStack &S,SElemType &e) { if (S.top==S.base)return 1 ; --S.top; e=*S.top; return 0 ; } int main () { SqStack st; char e,a[MAXSIZE];int n; InitStack (st); cin>>n; for (int i=0 ;i<n;i++) cin>>a[i]; for (int j=0 ;j<n;j++) Push (st,a[j]); for (int k=0 ;st.top!=st.base;k++) { Pop (st,e); cout<<e<<" " ; } return 0 ; }
题目2:顺序队列的基本操作 题目:
1 2 3 4 5 6 7 8 9 10 11 12 13 本关任务:对于顺序存储的字符队列完成两个操作函数,分别实现顺序循环队列的的入队和出队功能。 相关知识 (1)定义循环队列类型SqQueue; typedef struct { QElemType *base; int front; int rear; }SqQueue; (2)子函数InitQueue(SqQueue &sq),实现循环队列的初始化; (3)子函数EnQueue(SqQueue &sq,char x),实现循环队列中的入队运算; (4)子函数DeQueue(SqQueue &sq,char &x),实现循环队列中的出队运算; (5)编写主函数,在主函数中调用上述子函数,输出相应的结果。
答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include <iostream> using namespace std;#define MAXSIZE 10 #define OVERFLOW -2 typedef char QElemType;typedef struct { QElemType *base; int front; int rear; }SqQueue; void InitQueue (SqQueue &Q) { Q.base=new QElemType[MAXSIZE]; Q.front=Q.rear=0 ; } int EnQueue (SqQueue &Q,QElemType e) { if ((Q.rear+1 )%MAXSIZE==Q.front)return 1 ; Q.base[Q.rear]=e; Q.rear=(Q.rear+1 )%MAXSIZE; return 0 ; } int DeQueue (SqQueue &Q,QElemType &e) { if (Q.front==Q.rear)return 1 ; e=Q.base[Q.front]; Q.front=(Q.front+1 )%MAXSIZE; return 0 ; } int main () { SqQueue queues; InitQueue (queues); char a; int n; cin>>n; for (int i=0 ;i<n;i++) { cin>>a; EnQueue (queues,a); } for (int i=0 ;i<n;i++) { DeQueue (queues,a); cout<<a<<" " ; } }
题目3:用栈实现进制转换 题目:
1 2 3 4 5 6 7 8 9 10 11 12 13 本关任务:编写一个程序,实现对于任意一个非负十进制数,打印输出与其等值的八进制数。 相关知识 算法基本思想: 当将一个十进制整数N转换为八进制数时,在计算过程中,把N与8求余得到的八进制数的各位依次进栈,计算完毕后将栈中的八进制数依次出栈输出,输出结果就是待求得的八进制数。 算法描述: ①初始化一个空栈S。 ②当十进制数N非零时,循环执行以下操作: ●把N与8求余得到的八进制数压入栈S; ● N更新为N与8的商。 ③当栈S非空时,循环执行以下操作: ●弹出栈顶元素e; ●输出e。
答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #include <iostream> using namespace std;#define MAXSIZE 10 #define OVERFLOW -2 typedef int SElemType;typedef struct { SElemType *base; SElemType *top; int stacksize; }SqStack; void InitStack (SqStack &S) { S.base=new SElemType[MAXSIZE]; S.top=S.base; S.stacksize=MAXSIZE; } int Push (SqStack &S,SElemType &e) { if (S.top-S.base==S.stacksize)return OVERFLOW; *S.top=e; S.top++; return 0 ; } int Pop (SqStack &S,SElemType &e) { if (S.top==S.base)return 1 ; --S.top; e=*S.top; return 0 ; } int main () { SqStack stk; InitStack (stk); int n,e,s; cin>>n; while (n!=0 ) { s=n%8 ; Push (stk,s); n=n/8 ; } for (int i=0 ;stk.top!=stk.base;i++) { Pop (stk,e); cout<<e; } }
题1.向顺序栈中压入新元素,应当() A.先移动栈顶指针,再存入元素 B. 先存入元素,再移动栈顶指针 C.先后次序无关紧要 D. 同时进行 答案:A 题2.链栈对比顺序栈主要优点在于() A.通常不会出现栈满的情况 B. 通常不会出现栈空的情况 C. 插入操作更加方便 D. 删除操作更加方便 答案:A 解析:链栈的优点是便于多个栈共享存储空间和提高其效率,且不存在栈满上溢的情况 题2.将一个递归算法改为对应的非递归算法时,通常需要使用() A.栈 B.队列 C.循环队列 D.优先队列 答案:A 解析:栈能适用于递归算法,表达式求值以及括号匹配等问题
6.树、二叉树 树:n个结点的有限集 每个数据元素只可能有一个直接前驱但是可以有多个直接后继 祖先、双亲、孩子 结点的度:该结点的孩子个数 树的度:树中结点的最大度数 分支结点(非终端结点)度大于0 叶子结点(终端结点)度等于0 兄弟结点:具有相同双亲的结点 深度:根结点从叶子结点向下 高度:叶子结点到根结点向上 有序树:从左到右有次序 无序树:从左到右无次序 路径:两个结点之间经过的结点序列 路径长度:路径上经过的边的条数
树的性质: 1、树中结点树等于所有结点的度数+1 2、度为m的树中第i层上至多有m^(i-1)个结点 题2.对一棵n个结点的树,则该树中结点的度数之和为() A. n B. n-1 C.n+1 D. 不确定 答案:B
题3.设一棵度为3的树中有2个度数为1的结点,2个度数为2的结点,2个度数为3 的结点,则该树中有()个度数为0的结点 A. 5 B. 6 C. 7 D. 8 答案:C 解析:设树中有x个度数为0的结点 2+2+2+x=2×1+2×2+2×3
二叉树 满二叉树:二叉树结点都是满的 完全二叉树:叶子结点不一定是满的,但是后继都是2个
性质1:非空二叉树上的叶子结点数等于度为2的结点数加1,即n0=n2+1 题1.一棵二叉树有67个结点,这些结点的度要么是0,要么是2。这棵二叉树中二叉树中度为2的结点有()个 n0+n2=67 -> n2+n2+1=67
题2.将一棵有100个结点的完全二叉树,从根这一层开始,每一层从左到右依次 对结点编号,根结点的编号为1,则编号为49的结点的双亲编号为() A. 23 B. 25 C. 24 D.无法确定 答案:C 解析:49/2取整
二叉树存储结构 (1)顺序存储结构:指用一组地址连续的存储单元依次自上而下、自左至右存储完全二叉树的结点元素,即将完全二叉树上编号为i的结点元素存储在一维数组下标i-1的分量中 (2)链式存储结构:用链表结点来存储二叉树中的每个结点 每个结点:lchild(左孩子地址)、data(数据)、rchild(右孩子地址)
1 2 3 4 typedef struct BiTNode {ElemType data; struct BiTNode *Ichild , *rchild ;} BiTNode, *BiTree;
在含有n个结点的二叉链表中,含有n+1个空链域,含有n-1个非空链域
二叉树的遍历 原则:先左子树再右子树 先序遍历:根、左、右
1 2 3 4 5 6 7 void PreOrder (BiTree T) { if (T != NULL ) { visit(T); PreOrder(T->Ichild); PreOrder(T->rchild) ; } }
中序遍历:左、根、右
1 2 3 4 5 6 7 void InOrder (BiTree T) { if (T != NULL ) { InOrder (T->Ichild) ; visit(T); InOrder (T->rchild) ; } }
后序遍历:右、根、左
1 2 3 4 5 6 7 void PostOrder (BiTree T) { if (T != NULL ) { PostOrder (T->Ichild); PostOrder (T->rchild) ; visit(T); } }
层次遍历——队列
1 2 3 4 5 6 7 8 9 10 11 12 13 void LevelOrder (BiTree T) { InitQueue (Q) ; BiTNode *p=T; EnQueue (Q, p) ; while (!IsEmpty (Q)) { DeQueue (Q, p) ; visit(p); if (p->Ichild != NULL ) EnQueue (Q, p->Ichild); if (p->rchild != NULL ) EnQueue(Q,p->rchild); } }
题1.已知一棵二叉树的先序遍历结果为ABCDEF,中序遍历结果为 CBAEDF, 则后序遍历的结果为() A. CBEFDA B. FEDCBA C. CBEDFA D. 不定 答案:A 解析:根据逻辑判断 找根结点:后序根结点一定是最后访问的,先序的根结点一定是第一个访问的
题2.某二叉树结点的中序序列为:ABCDEFG,后序序列为:BDCAFGE,则其根结点 左子树中结点数目为() A. 3 B. 2 C. 4 D. 5
实训 题目1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 本关任务:编写一个程序,先由先序遍历序列建立一棵二叉树,再分别实现二叉树先序、中序、后序遍历的递归算法,最后利用后序遍历方法实现求解二叉树深度的操作。 相关知识 为了完成本关任务,你需要掌握:1. 如何由先序遍历序列建立一棵二叉树,2. 如何使用递归算法实现遍历。 由先序遍历序列建立一棵二叉树 如图所示二叉树,输入先序序列为: ABC##DE#G##F### 采用以下函数实现二叉树的二叉链表结构创建 void CreateBiTree (BiTree &T) { char ch; cin >> ch; if (ch=='#' ) T=NULL ; else { T=new BiTNode; T->data=ch; CreateBiTree(T->lchild); CreateBiTree(T->rchild); } }
答案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 #include <iostream> using namespace std; typedef struct BiNode { char data; struct BiNode *lchild,*rchild; }BiTNode,*BiTree; void CreateBiTree (BiTree &T) { char ch; cin >> ch; if (ch=='#' ) T=NULL ; else { T=new BiTNode; T->data=ch; CreateBiTree (T->lchild); CreateBiTree (T->rchild); } } void PreOrderTraverse (BiTree T) { if (T) { cout<<T->data; PreOrderTraverse (T->lchild); PreOrderTraverse (T->rchild); } } void InOrderTraverse (BiTree T) { if (T) { InOrderTraverse (T->lchild); cout<<T->data; InOrderTraverse (T->rchild); } } void postOrderTraverse (BiTree T) { if (T) { postOrderTraverse (T->lchild); postOrderTraverse (T->rchild); cout<<T->data; } } int Depth (BiTree T) { int dep1=0 ,dep2=0 ; if (T) { dep1=Depth (T->lchild); dep2=Depth (T->rchild); if (dep1>dep2)return (dep1+1 ); else return (dep2+1 ); } else return 0 ; } int main () { BiTree tree; CreateBiTree (tree); cout<<"先序序列:" ; PreOrderTraverse (tree); cout<<endl; cout<<"中序序列:" ; InOrderTraverse (tree); cout<<endl; cout<<"后序序列:" ; postOrderTraverse (tree); cout<<endl; cout<<"二叉树的深度:" ; cout<<Depth (tree); return 0 ; }
题目2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 本关任务:编写一个程序,实现用二叉树表示算术表达式,通过对二叉树的遍历,中缀表达式的输出。 相关知识 为了完成本关任务,你需要掌握:1. 如何用二叉树表示算术表达式,2. 如何正确输出中缀表达式。 用二叉树表示算术表达式 一个算术表达式可以用二叉树表示,这个二叉树有2 个特点: 1. 叶子结点一定是操作数2. 分支结点一定是运算符一棵二叉树可以转换为等价的中缀表达式,并且为了反映运算符的计算次序应该适当加上括号。 如图所示二叉树转换为等价的中缀表达式为: a + b * (c – d) – e / f 输出中缀表达式 一个算术表达式可以用二叉树表示,这个二叉树的中序遍历序列加上必要的括号即为中缀表达式,在中序遍历二叉树的过程中,如果当前访问的是叶子结点,则无需加括号,可以直接输出。如果当前访问的结点深度大于1 ,则对左子树递归调用之前要加上左括号,对右子树递归调用之后要加上右括号。 算法基本思想: 输入:二叉链表根指针T,二叉树的深度deep 输出:中缀表达式 1. 若指针T为空,则算法结束;2. 否则执行下述操作:2.1 若deep>1 ,则输出左括号;2.2 deep++;递归调用左子树;2.3 输出T->data;2.4 deep++;递归转换右子树;2.5 若deep>1 ,则输出右括号;
答案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <iostream> using namespace std; typedef struct BiNode { char data; struct BiNode *lchild,*rchild; }BiTNode,*BiTree; void CreateBiTree (BiTree &T) { char ch; cin >> ch; if (ch=='#' ) T=NULL ; else { T=new BiTNode; T->data=ch; CreateBiTree (T->lchild); CreateBiTree (T->rchild); } } void InExpression (BiTree T, int depth = 1 ) { if (T) { if ((T->lchild or T->rchild) and depth>1 )cout<<"(" ; InExpression (T->lchild,depth+1 ); cout<<T->data; InExpression (T->rchild,depth+1 ); if ((T->lchild or T->rchild) and depth>1 )cout<<")" ; } } int main () { BiTree tree; CreateBiTree (tree); cout << "中缀表达式结果:" ; InExpression (tree); cout << endl; return 0 ; }
树和森林 树的存储结构:双亲表示法、孩子表示法、孩子兄弟表示法 森林:多棵树的一个集合
森林转换成二叉树的画法:(左孩子右兄弟) (1)将森林中的每棵树转换成相应的二叉树 (2)每棵树的根也可视为兄弟结点,在每棵树之间加一根连线 (3)以第一棵树的根为轴心顺时针旋转 45° 题4.设森林F中有三棵树,第一、第二、第三棵树上的结点个数分别为M1,M2,M3 则与森林F对应的二叉树根结点的右子树上的结点个数为() A. M1 B. M1 + M2 C. M3 D. M2+ M3 答案:D
二叉树转化为森林就是反过来做上面的三个步骤
哈夫曼树 权:树中结点常被赋予一个代表某种意义的数值 结点带权路径长度:从树的根到任意结点的路径长度与该结点上权值的乘积 树的带权路径长度:树中所有叶结点的带权路径长度之和,记作$WPL = \sum_{i = 1}^{n} w_i l_i$(自己的数字乘线段个数) 哈夫曼树(最优二叉树):树的带权路径长度最小的二叉树 构造哈夫曼树的步骤: (1)将所有结点分别作为仅含一个结点的二叉树 (2)构造一个新结点,从中选取两棵根结点权值最小的树作为新结点的左、右子树,并且将新结点的权 值置为左、右子树上根结点的权值之和 (3)从中删除刚才选出的两棵树,同时将新得到的树加入森林中 (4)重复步骤(2)和(3),直至剩下一棵树为止
题1.有一电文使用五种字符a,b,c,d,e,其出现频率依次为4,7,5,2,9 (1)试画出对应的哈夫曼树(要求左子树根结点的权小于等于右子树根结点的权) (2)求出每个字符的哈夫曼编码 (3)译出编码序列11000111000101011 的相应电文 (4)求带权路径长度
7.图 有向图:每条边都有方向<v,w>这个表示v到w的一条有向边 无向图:每条边都没方向(v,w)表示一条无向边 完全图:对于无向图,任意两个顶点都存在边n*(n-1)/2;对于有向图,任意两个顶点之间都存在方向相反的两条弧n*(n-1) 子图:图是子集 连通:两个点之间有路径连接就是连通的 连通图:图中任意两个顶点都是连通的 左边是非连通图,右边是连通图
顶点的度 无向图:一个顶点有几条线就有多少度,无向图的全部顶点的度的和等于边数的两倍 对于有向图有入度和出度,入度是以顶点v为终点的有向边的数目,出度就是以顶点v为起点的有向边的数目 顶点的度等于入度和出度之和,有向图的全部顶点入度之和和出度之和相等,并且等于边数
边的权和网 每条边加入数值,相当于就是权值,有权值就叫带权图/网
路径、回路 回路:可以兜圈的
图的存储结构 邻接矩阵法 1,1不存在 1,2存在 1,3不存在 1,4存在 1,5不存在 依次类推 有向图注意加了方向,方向不对不算连接 带权图表示方式不一样
使用邻接矩阵存储方法: n个顶点需要n个单元存储顶点信息,n^2个单元存储边(弧)。 优点: 容易实现图的操作,如:求某顶点的度、判断顶点之间是否有边(弧)、找顶点的邻接点等等。 缺点: 边或弧很少的稀疏图,仍需n+n2个存储空间,造成空间浪费。
题1. n个顶点的无向连通图用邻接矩阵表示时,该矩阵至少有()个非零元素 答案:2(n-1) 题2.带权有向图G用邻接矩阵A存储,则顶点i的入度等于A中() A. 第i行非无穷元素之和 B. 第i列非无穷元素之和 C. 第i行非零非无穷元素之和 D. 第i列非零非无穷元素之和 答案:D 题3.用邻接矩阵存储一个图时,在不考虑压缩存储的情况下,所占用的存储空间大小只与图中顶点的个数有关,而与图的边数无关() 答案:对
邻接表法 顶点表结点:顶点域、边表头指针 存边结点:邻接点域、指针域 表里的顺序可以随意调换(比如2,无论是1534还是1345都行,只要表示出一个结点连了多少条边) 使用邻接表存储方法: 对于n个顶点,e条边的图: 无向图的邻接表需要n个头结点,2e个边结点。 有向图的邻接表需要n个头结点,e个边结点。
优点: 空间效率高;容易寻找顶点的邻接点; 缺点: 判断两顶点间是否有边或弧,需搜索两结点对应的单链表,没有邻接矩阵方便。
若G为无向图,则所需的存储空间为O(|V|+2|E|) 若G为有向图,则所需的存储空间为O(|V|+|E|)
题5.在有向图的邻接表表示中,下面()最费时间 A. 求某顶点的出度 B. 求某顶点的入度 C. 求图中顶点的个数 D. 求从某顶点出发的弧 答案:B
图的遍历 图的遍历:从图中的某一顶点出发,按照某种搜索方法沿着图中的所有顶点访问一次且仅访问一次 图的遍历算法主要有两种:广度优先搜索和深度优先搜索
(1)广度优先搜索(bfs) 类似于二叉树的层序遍历算法 注意这里的顺序 先是1与2345相邻(12345) 然后再判断2与7相邻 然后3无 4与6相邻 所以选C
(2)深度优先搜索(dfs) 类似于树的先序遍历 题2.深度优先搜索遍历类似于树的(先序)遍历,广度优先搜索遍历类似于树的(层次)遍历,它们可以用(栈、队列)两种数据结构来实现。 题3. 若无向图 G 中的边的集合E={(a,b),(a,e),(a,c),(b,e),(e,d),(d,f),(f,c)},则 从顶点a出发进行深度优先遍历,可以得到的一种顶点序列为() A. aedfcb B. acfebd C. aebcfd D. aedfbc 注意:下一个点任意,找不到未访问点就返回上一步(回溯),直到遍历完为止 答案:A
图的应用 最小生成树 带权连通无向图中所有生成树中权值最小的生成树
Prim算法: 1、任取一顶点,去掉所有边 2、选择一个与当前顶点集合距离最近(权值最小)的顶点,并将该顶点和相应的边加入进来,同时不能形成回路 3、重复2,直至图中所有顶点都并入
Kruskal 算法: 1、去掉所有边 2、选边(权最小,且不构成回路) 3、重复2,直至图中所有顶点都并入 Prim:(1,3),(3,6),(6,4),(3,2),(2,5) Kruskal:(1,3),(4,6),(2,5),(3,6),(2,3)
最短路径 dijkstra算法 d(u,u)=0 当u和v不相邻时d(u,v)=+无穷 先求v1,因为是起点,所以不管,加入表 然后接着填表 发现3最小,所以最短路径是v2,把v2加入表中,不需要计算后续步骤 第三步从v1到v2到v3权值是5,比7要小,所以加入表格 v1到v2到v5为9,比正无穷小,加入 这里选v3加入 每次下一步都是上一步的最小值相加
拓扑排序 AOV网:顶点表示活动<Vi,Vj> 拓扑排序: 1、每个顶点出现且只出现一次 2、若存在一条从顶点A到顶点B的路径,则在排序中顶点B出现在顶点A的后面 拓扑排序的算法的步骤: 1从AOV网中选择一个没有前驱的顶点并输出 2从网中删除该顶点和所有以它为起点的有向边 3重复1和2直到当前的AOV网为空或当前网中不存在无前驱的顶点为止
8.查找 (1)查找 在数据集合中,寻找满足某种条件的数据元素的过程 (2)查找表(查找结构) 用于查找的数据集合 (3)关键字 数据元素中某个数据项的值,用它可以标识一个数据元素 - 主关键字:关键字可以唯一地标识一个记录 (4)平均查找长度 所有查找过程中进行关键字的比较次数的平均值 $ASL = \sum_{i = 1}^{n} P_i C_i$
顺序查找 从头开始一个一个找,找到就返回,没找到就返回失败 题1.采用顺序查找方法查找长度为n的顺序表时,查找成功的平均查找长度为() A. n B. n/2 C. (n+1)/2 D. (n-1)/2 答案:C 拓展:不成功平均是n+1
实训 题目:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 本关任务:编写程序,实现顺序表上的顺序查找。 相关知识 实训中的类型定义及子函数的作用如下: (1 )定义顺序表中数据元素类型; typedef int ElemType;typedef int KeyType;typedef struct { KeyType key; ElemType data; } SqType; (2 )子函数Create_SSTable(SqType R[]) ,实现查找表的创建,从键盘上依次输入整型数据元素,创建查找表; (3 )子函数SqSearch(SqType R[],int n,KeyType k),实现顺序查找; (4 )子函数Show_End(int result,int testkey),输出查找的结果; (5 )主函数,在主函数中调用上述子函数,输出相应的结果。
答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include <iostream> using namespace std ; #define MAXSIZE 8 #define OK 1; typedef int ElemType;typedef int KeyType;typedef struct { KeyType key; ElemType data; } SqType; int Create_SSTable (SqType R[]) { for (int i=0 ;i<MAXSIZE;i++) { cin >>R[i].key;} return 1 ; } int SqSearch (SqType R[],int n,KeyType k) { for (int i=0 ;i<MAXSIZE;i++) { if (R[i].key==k)return i+1 ; } return 0 ; } void Show_End (int result,int testkey) { if (result==0 ) cout <<"未找到" <<testkey<<endl ; else cout <<"找到" <<testkey<<"位置为" <<result<<endl ; } int main () { SqType R[MAXSIZE]; Create_SSTable(R); int n=sizeof (R)/sizeof (R[0 ]); int testkey1,testkey2,result; cin >>testkey1; result=SqSearch(R,n,testkey1); Show_End(result,testkey1); cin >>testkey2; result=SqSearch(R,n,testkey2); Show_End(result,testkey2); return 0 ; }
折半查找(二分查找) 仅适用于有序的顺序表 先给数据排序(例如按升序排好),形成有序表。 取中间记录R[mid].key与给定值key作比较, 若R[mid].key == key,则查找成功,返回mid; 若key<R[mid].key,则在中间记录的前半区继续查找; 若key>R[mid].key,则在中间记录的后半区继续查找。 重复上述过程,直到查找成功,或表中无此记录,查找失败。 是不是很像高中学的二分法hhh 注意:填空题大家可以转成树做更简单(二叉排序树) 题1.二分查找要求结点() A. 有序、顺序存储 B. 有序、链式存储 C. 无序、顺序存储 D. 无序、链式存储 答案:A 题2.采用对分查找序列数据(6,15,30,37,65,78,89,90,97),若查找元素89时比较次数为() 答案:2
实训 题目:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 本关任务:编写程序,实现有序顺序表上的折半查找。 相关知识 实训中的类型定义及子函数的作用如下: (1 )定义顺序表中数据元素类型; typedef int ElemType;typedef int KeyType;typedef struct { KeyType key; ElemType data; } SqType; (2 )子函数Create_SSTable(SqType R[]) ,实现查找表的创建,从键盘上依次输入有序的整型数据元素,创建查找表; (3 )子函数BinSearch(SqType R[],int n,KeyType k),实现折半查找; (4 )子函数Show_End(int result,int testkey),输出查找的结果; (5 )主函数,在主函数中调用上述子函数,输出相应的结果。
答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <iostream> using namespace std;#define MAXSIZE 8 #define OK 1; typedef int ElemType;typedef int KeyType;typedef struct { KeyType key; ElemType data; } SqType; int Create_SSTable (SqType R[]) { for (int i=0 ;i<MAXSIZE;i++) {cin>>R[i].key; } return 1 ; } int BinSearch (SqType R[],int n,KeyType k) { int low=0 ,high=n-1 ,mid; while (low<=high) { mid=(low+high)/2 ; if (R[mid].key==k)return mid+1 ; else if (R[mid].key>k)high=mid-1 ; else low=mid+1 ; } return 0 ; } void Show_End (int result,int testkey) { if (result==0 ) cout<<"未找到" <<testkey<<endl; else cout<<"找到" <<testkey<<"位置为" <<result<<endl; } int main () { SqType R[MAXSIZE]; Create_SSTable (R); int n=sizeof (R)/sizeof (R[0 ]); int testkey1,testkey2,result; cin>>testkey1; result=BinSearch (R,n,testkey1); Show_End (result,testkey1); cin>>testkey2; result=BinSearch (R,n,testkey2); Show_End (result,testkey2); return 0 ; }
散列表 散列函数: $Hash(key) = Addr$ 同义词: 散列函数可能会把两个或两个以上的不同关键字映射到同一地址 (1)直接定址法: $H(key) = key$ $H(key) = a \times key + b$ (2)除留余数法: 取一个不大于但最接近或等于 $m$ 的质数 $p$ 公式:$H(key) = key % p$ (3)数字分析法 (4)平方取中法 例题: 线性探测就是往后一个一个挪 注意不成功:地址个数就是取模的后面那个数,前面的是对应空的比较次数(0就是1次,1相距最近的空位置距离为13,2为12,依次类推) 存放方式:拉链法,用线性链表存储 二次探测法:
by 梁家诺