基本概念和术语:
数据:是对客观事物的符号表示,在计算机科学中是指所有能输入到计算机并被计算机程序处理的符号总称。
数据元素: (有时也称为结点,顶点,记录)是数据的基本单位,在计算机程序中通常作为一个整体进行考虑和处理。
数据对象:是性质相同的数据元素的集合,是数据的一个子集
数据结构及其形式化描述
数据结构:是带有结构的数据元素的集合。||是相互之间存在一种或多种特定关系的数据元素的集合。
数据类型
1、数据类型:是一个值的集合和定义在该值上的一组操作的总称。
抽象数据类型:是指一个数据结构以及定义在该结构上的一组操作。
由用户定义,用以表示应用问题的数据模型。它与数据类型实质上是一个概念,但其特征是使用与实现分离,实行封装和信息隐蔽。
算法的特征和要求,掌握算法的时间复杂度和空间复杂度
算法是对特定问题求解步骤的一种描述,它是指令的有限序列。
有五个重要的特性:有穷性,确定性,可行性,输入,输出。
时间复杂度:算法中基本操作重复的次数。
线性表
线性结构特点:若结构是非空有限集,则有且仅有一个开始结点和一个终端结点,除首尾结点外,其他结点只有一个直接前驱和一个直接后继。
基本操作:
初始化:InitList(&L)
删除表:DestroyList(&L)
表置空:ClesrList(&L)
表判空:ListEmpty(L)
表长度:ListLength(L)
取元素:GetElem(L,i,&e)
定位:LocateElem(L,e,compare())
求前驱:PriorElem()
求后继:NextElem()
前插操作:ListInsert()
删除操作:ListDelete()
顺序表的随机存取;
线性表的顺序存储结构是一种随机存取的存储结构。
- 顺序表为空、为满的判定(见2.算法);
- 顺序表的插入和删除操作(算法);
插入:
Status Insert_Sq (Sqlist &L,int i ,ElemType e) { int j ; ElemType *p,*q; ElemType * newbase; if ( i<1 || i >L.length+1) return ERROR ; if (L.length>=L.listsize)//判断表是否已满 { newbase = (ElemType * )realloc (L.elem, ( L.listsize + LISTINCREMENT) * sizeof(ElemType)); if (!newbase) exit (OVERFLOW); L.elem = newbase; L.listsize += LISTINCREMENT; } q = &(L.elem[i-1]); //q为插入位置 for (p =& (L.elem[L.length-1]);p >= q; --p) *(p+1)= *p; //插入位置及之后元素右移 *q = e; ++L.length; return OK;}
删除:
Status ListDelete_Sq(SqList &L, int i, ElemType &e){ //在顺序表L中删除第i个元素,并用e返回其值 ElemType *p; ElemType *q; if ( i < 1 || i > L.length ) return ERROR ; //i值不合法 p = &(L.elem[i-1]); //p为被删除元素的位置 e = *p; //被删除元素的值赋给e q =L.elem+L.length-1; //表尾元素的位置 for ( ++p; p <= q; ++p) *(p - 1) = *p; //被删除元素之后的元素左移 --L.length; return OK;}
2、掌握有关线性表链式存储的内容:
(1)单链表为空的判定(带头结点、不带头结点);
(2)单链表的查找(算法);时间复杂度T(n) = O(n)。
Status GetElem_L(LinkList L,int i,ElemType &e){ LinkList p; int j; p=L->next; j=1; //初始化,p指向首元结点,计数器j初值为1 while(p&&jnext; //p指向下一个结点 ++j; //计数器j相应加1 } if(!p || j>i) return ERROR; //i值不合法i>n或i<=0 e=p->data; //取第i个结点的数据域 return OK; }
(3)单链表的插入、删除操作(算法)。
插入:-----
//在带头结点的单链表L中第i个位置插入值为e的新结点 Status ListInsert_L(LinkList &L,int i,ElemType e){ LinkList p,s; int j; p=L; j=0; while(p&& (j
next; ++j;//查找第i-1个结点,p指向该结点} if(!p ||j>i-1) return ERROR; s=(LinkList)malloc(sizeof(LNode));//生成一个新的结点*s s->data=e;//将结点*s的数据域设置为e s->next=p->next;//将结点*s的指针域指向结点b p->next=s;//将结点*p的指针域指向结点*s return OK; } 删除:
//在带头结点的单链表L中,删除第i个元素,并由e返回其值 Status ListDelete_L(LinkList &L,int i,ElemType e){ LinkList p,q; int j; p=L; j=0; while((p->next)&&(j
next; ++j; } if(!(p->next) || (j>i-1)) return ERROR;//当i>n或i<1时,删除位置不合理 q=p->next;//临时保存被删除结点的地址以备释放 p->next=q->next;//改变删除结点前驱结点的指针域 e=q->data; free(q);//释放删除结点的空间 return OK; }
栈和队列
1、顺序栈的表示,栈操作的特点以及为空、为满的判定;
base 表示栈底指针,用 top 指示栈顶指针
判空:S.top == S.base
栈满:S.top-S.base = S. stacksize
2、栈的入栈和出栈操作(算法);后进先出,先进后出
入栈:
Status Push(SqStack &S,SElemType e){ //插入元素e为新的栈顶元素 if(S.top-S.base>=S.stacksize)//栈满 {S.base=(SElemType*)realloc(S.base,(S.stacksize+STACKINCREMENT)*sizeof(SElemType)); if (!S.base) exit (OVERFLOW);//存储分配失败 S.top=S.base+S.stacksize; S.stacksize+=STACKINCREMENT; } *S.top++=e; return OK;}
出栈:
Status Pop(SqStack &S,int &e){ //若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR if(S.top==S.base)return ERROR; e=*--S.top; return OK; }
3、队列的表示以及循环队列为空、为满判定;
◆方法一 :用一个计数器来记载队列中的元素个数。
• 初始化队列时c = 0;
• 当入队时,计数变量+1( c = c+1 )
• 当出队时,计数变量-1 (c = c-1)
• 当计数变量 = maxsize时,队满
• 当计数变量 = 0时,队空
◆ 方法二:设一个标志位用来区别队列是空还是满。
• 初始化队列时:Q.front = Q.rear,标志位为 false(0)
• 入队置标志位为true(1)
• 出队置标志位为false(0)
• 当Q.front = Q.rear, 且标志位为true时,队满。
• 当Q.front = Q.rear, 但标志位为false时,队空。
• 其他为非空非满
◆ 方法三:牺牲一个元素空间,来区别队空或队满。
约定入队前,测试尾指针在循环意义下加1后是否等
于头指针,若相等则认为队满。即:
• 循环队列为空:front = rear 。
• 循环队列满:(rear+1)%MAXQSIZE =front。
4、顺序队列的入队和出队操作(算法)。先进先出
入队:
Status EnQueue(LinkQueue &Q,QElemType e) { //插入元素e为Q的新的队尾元素 QueuePtr p; p=(QueuePtr)malloc(sizeof(QNode)); if(!p) exit(OVERFLOW); p->data=e; p->next=NULL; Q.rear->next=p; Q.rear=p; return OK; }
出队:
Status DeQueue(LinkQueue &Q,QElemType &e) { //若队列不空,则删除Q的队头元素,用e返回其值,并返回OK;否则返回ERROR QueuePtr p; if(Q.front==Q.rear)return ERROR; p=Q.front->next; e=p->data; Q.front->next=p->next; if(Q.rear==p) Q.rear=Q.front; free(p); return OK; }
串
掌握串的特点(元素受限),串长度、空串、串的位置、串相等、空格串几个概念即可。
- 串的逻辑结构和线性表极为相似,区别仅在于串的数据对象约束为字符集;
- 串中字符的数目n称为串的长度;
- 零个字符的串称为空串;
- 位置:
(1)单个字符在主串中的位置 被定义为该 字符在串中的序号;
(2)子串在主串中的位置 被定义为主串中首次出现的该子串的第一个字符在主串中的位置;
5.两个字符串相等 的充分必要条件为两个字符串的长度相等,并且对应位置上的字符相同;
6.由一个或多个空格组成的串’ ’称为空格串。
树和二叉树
1.理解有关树的概念和术语;
树是一类重要的非线性数据结构
2.理解二叉树的链式存储结构,
1.二叉链表
2.三叉链表
3.掌握完全二叉树的基本概念;
深度为k,结点数为n的二叉树,当且仅当每个结点的编号都与相同深度的满二叉树中从1到n的结点一一对应时,称为完全二叉树。
完全二叉树的特点:
(1)叶结点只可能出现在层次最大的两层(最下两层)
(2)对任一结点,若其右下分支的子孙的最大层次为l,则其左分支下的最大层次为l或者l+1
✓ 最下层的叶子一定集中在左边连续位置
✓ 倒数二层若有叶子结点,一定都在右边连续位置
✓ 如果结点度为1,则该结点只有左孩子
4、重点掌握二叉树的六大性质,并会灵活运用;
性质1: 在二叉树的第i层上至多有2i-1个结点(i≥1)
性质2: 深度为k的二叉树至多有2k-1个结点(k ≥1)(深度一定,二叉树的最大结点数也确定)
性质3: 对于任何一棵二叉树,终端结点数n0与度为2的结点数n2有如下关系: n0=n2+1
性质4:结点数为n的完全二叉树,其深度为└log2n ┘ + 1
性质5:对完全二叉树,若从上至下、从左至右编号,则编号为 i 的结点,其左孩子编号必为 2i,其右孩子编号必为2i+1;其双亲的编号必为 └i/2 ┘(i=1 时为根,除外)。
性质6:含有n个结点的二叉链表中,有n+1个空链域。
5、掌握二叉树的各种遍历方法:
- 能够根据二叉树写出各种遍历序列;
基本操作,有手就行。
- 能够由两种遍历序列(必须包含中序序列)恢复二叉树;
已知中序和先序或已知中序和后序
6.熟悉线索二叉树;
规定:
1)若结点有左子树,则lchild指向其左孩子; 否则, lchild指向其直接前驱(即线索);
2)若结点有右子树,则rchild指向其右孩子; 否则, rchild指向其直接后继(即线索) 。
为区别两种不同情况,特增加两个标志域(各1bit)当Tag域为 0 时,表示 正常 情况;
当Tag域为 1 时,表示 线索 情况。
线索链表:用含Tag的结点样式所构成的二叉链表
线 索:指向结点前驱和后继的指针
线索二叉树:加上线索的二叉树
线 索 化:对二叉树以某种次序遍历使其变为线索二叉树的过程
7 、树和森林与二叉树的转换树转换为二叉树转换步骤:
step1: 将树中同一结点的兄弟相连; 加线
step2: 保留结点的最左孩子连线,删除其它孩子连线; 抹线
step3: 将同一结点的孩子连线绕左孩子旋转45度角。 旋转
二叉树转换为树:把右孩子转换为兄弟。
森林转换为二叉树的方法
法一:
① 各树先各自转为二叉树;
② 依次连到前一个二叉树的右子树上。
法二:森林直接变兄弟,再转为二叉树
二叉树转换为森林的方法:把最右边的子树变为森林,其余右子树变为兄弟
树与二叉树的遍历
1. 树的先序遍历与对应二叉树的先序遍历相同;
2. 树的后序遍历相当于对应二叉树的中序遍历;
3. 树没有中序遍历,因为子树无左右之分。
8、掌握 Huffman 树的相关内容:给定字符及频率构造 Huffman 树,设计 Huffman编码;
赫夫曼树的特点:
1、不存在度为1的结点
2、若给定权值的叶子结点有n个,则构造的赫夫曼树的总结点数为 2n-1
举个栗子:
假设有5个符号以及它们的频率,求前缀编码
A B C D E
6 7 2 5 9
解:
1、熟悉图的定义和术语(有向图、无向图、完全图);
无向图: 图G中的每条边都是无方向的;
有向图: 图G中的每条边都是有方向的,称为弧(Arc)
完全图: 边达到最大的图
顶点度数之和/2=边数
连通图 :在无向图中如果任意一对顶点都是连通的, 则称此图是连通图。
强连通图 :在有向图中, 若对于每一对顶点vi和vj ,都存在一条从vi到vj和从vj到vi 的路径, 则称此图是强连通图。
无向图中极大连通子图叫做连通分量。
有向图中的极大强连通子图叫做强连通分量。
设n为顶点数,e为边或弧的条数
对无向图有:0 ≤ e ≤ n(n-1)/2
有向图有:0 ≤ e ≤ n(n-1)
2、掌握图的邻接矩阵o(n^2)和邻接表o(n+e)两种存储结构:
(1)掌握存储结构的特点(是否对阵、求边数、求结点度等);
无向图:(邻接矩阵)
•无向图的邻接矩阵是对称的;
•判定两个顶点Vi与Vj是否关联,只需判A[i,j]是否为1
•顶点i 的度=第 i 行 (列) 中1 的个数;
•邻接矩阵主对角元素为0;
•特别:完全图的邻接矩阵中,主对角元素为0,其余全1。
有向图:(邻接矩阵)
•有向图的邻接矩阵可能是不对称的。
•顶点的出度=第i行元素之和,
•顶点的入度=第i列元素之和。
•顶点的度=第i行元素之和+第i列元素之和,
(邻接表)
•无向图顶点的度=单链表中链接的结点个数
•有向图顶点的出度=单链出边表中链接的结点数
•有向图顶点的入度=邻接点为Vi的弧个数
(2)能够画出给定图的存储结构,或者根据存储结构画出图的逻辑结构;
(3)能够写出在两种存储结构上进行深度优先和广度优先遍历的序列;
深度优先遍历(DFS):基本思想:——仿树的先序遍历过程。
访问指定的某顶点v,将v作为当前顶点;
访问与v邻接但还未被访问过的顶点,并以该邻接点作为当前顶点;
重复2,直到所有和当前顶点有路径相通的顶点都被访问到。沿搜索路径回退,退到前一次刚访问过的顶点,看是否还有其它没有被访问的邻接顶点。如果有,访问此顶点,之后再从此顶点出发,进行与前述类似的访问;如果没有,就再退回一步进行搜索。重复上述过程,直到 连通图中所有顶点都被访问过为止。
广度优先遍历(BFS):基本思想:——仿树的层次遍历过程。
• 在访问了起始点v之后,依次访问v的邻接点;
• 然后再依次访问这些顶点中未被访问过的邻接点;
• 直到所有顶点都被访问过为止
3、掌握构造最小生成树的 Prim 算法(归并顶点)和 Kruskal 算法(归并边),能够按照步骤构造最小生成树;
Prim 算法(归并顶点):
Kruskal 算法(归并边):
4、对于拓扑排序算法和关键路径算法,前者要求会写拓扑排序序列,后者略看即可;
拓扑排序:对一个有向无环图中的顶点排成一个具有前后次序的线性序列。
(1) 在网中,选取一个没有前驱的顶点输出;
(2) 删除该顶点和所有以它为弧尾的弧;
(3) 重复以上两步,直到 网中全部顶点都已输出(得到拓扑有序序列)
关键路径:从源点到汇点之间路径长度最长的路径。
1、理解顺序查找(线性查找)的过程,掌握折半查找(有序表的查找)和索引顺序查找(分块查找)的过程。
顺序查找:即用逐一比较的办法顺序查找关键字
int Search_seq(SSTable ST, KeyType key) { ST.elem[0].key =key; for( i=ST.length; ST.elem[ i ].key!=key; - - i ) return i; } 查找成功 :ST.elem[i].key= key 查找不成功 :i = 0
折半查找:
要 求:查找表为有序表
有序表:查找表中记录按关键字有序排列的表
折半的位置:mid= └ (low+high)/2 ┘
int Search_Bin ( SSTable ST, KeyType key ) { low = 1; high = ST.length; while (low <= high) { mid = (low + high) / 2; if (EQ (key , ST.elem[mid].key) ) return mid; else if ( LT (key , ST.elem[mid].key) ) high = mid - 1; else low = mid + 1; } return 0; } // Search_Bin
索引顺序查找:
① 确定待查记录所在块; (可以用顺序或折半查找)
② 在块内顺序查找。 (只能用顺序查找)2、重点掌握二叉排序树(BST)的定义和构造;
定义:(1)左子树的所有结点均小于根的值;
(2)右子树的所有结点均大于根的值;
(3)它的左右子树也分别为二叉排序树
特点:
① 查找过程与顺序结构有序表中的折半查找相似,查找效率高;
② 中序遍历此二叉树,将会得到一个关键字的有序序列(即实现了排序运算);
③ 如果查找不成功,能够方便地将被查元素插入到二叉树的叶子结点上,而且插入时只需修改指针而不需移动元素。——这种既查找又插入的过程称为动态查找。
二叉排序树既有类似于折半查找的特性,又采用了链表存储,因此,对于经常要进行查找、插入和删除记录的有序表,采用BST尤其合适。
删除结点:(删除35两种方法)
4.掌握哈希表的构造和哈希查找的过程:
哈希表:即散列存储结构。
散列法存储的基本思想:建立关键码字与其存储位置的对应关系,或者说,由关键码的值决定数据的存储地址。
优点:查找速度极快(O(1)),查找效率与元素个数n无关!
(1)哈希函数掌握“直接定址法”“除留余数法”;
直接定址法:
优点:以关键码key的某个线性函数值为哈希地址,不会产生冲突。
缺点:要占用连续地址空间,空间效率低。
除留余数法:
特点:以关键码除以p的余数作为哈希地址。
关键:如何选取合适的p?
技巧:若设计的哈希表长为m,则一般取p≤m且为质数(最好接近m)
(2)冲突处理函数掌握“开放定址法”中的线性探测再散列和二次探测再散列, 能够将给定的关键字填入哈希表的适当位置;Hi=(Hash(key)+di) mod m(m 为哈希表的长度)
开放定址法:
设计思路:有冲突时就去寻找下一个空的哈希地址,只要哈希表足够大,空的哈希地址总能找到,并将数据元素存入。
(1)线性探测法
一旦冲突,就找附近(下一个)空地址存入。
(2)二次探测法:
有例题;
1、插入排序:掌握直接插入排序、折半插入排序和希尔排序(根据增量序列分段插入排序)的基本思想和排序过程;
直接插入排序:
先将序列中第 1 个记录看成是一个有序子序列,
然后从第 2 个记录开始,逐个进行插入,直至整个序列有序。
折半插入排序:
折半插入排序在寻找插入位置时,不是逐个比较而是利用折半查找的原理寻找插入位置。待排序元素越多,改进效果越明显。
希尔排序:
基本思想:先将整个待排记录序列分割成若干子序列,分别进行直接插入排序,待整个序列中的记录 “基本有序”时,再对全体记录进行一次直接插入 排序。
技巧:子序列的构成不是简单地“逐段分割”,而是将相隔某个增量 dk 的记录组成一个子序列,让增量dk 逐趟缩短(例如依次取5, 3, 1),直到 dk=1 为止。
优点:让关键字值小的元素能很快前移,且序列若基本有序时,再用直接插入排序处理,时间效率会高很多。
2、交换排序:掌握起泡排序,掌握快速排序(选取枢轴,逆序交换,递归完成);
起泡排序:(小的浮起,大的沉底)
快速排序:
附设两个指针low和high,初值分别指向第一个记录和最后一个记录,设枢轴为key;
1.从high 所指位置起向前搜索,找到第一个不大于基准值的记录与枢轴记录相互交换;
2.从low 所指位置起向后搜索,找到第一个不小于基准值的记录与枢轴记录相互交换。
3.重复这两步直至low=high为止。
3、选择排序:理解简单选择排序。
简单选择排序:
4、归并排序
ASL:平均查找长度
WPL:带权路径长度