本人的期末数据结构期末考试复习整理的知识点,把知识点理解一遍,有条件的话再结合书后习题练习一下(特别是二叉树和图论),成绩90+很轻松
数据结构的主要研究内容是非数值问题
数据:客观事务的符号表示,是所有能够输入计算机并被计算机程序处理的符号的总称
数据元素:数据的基本单位,在计算机中通常以一个整体进行考虑和处理
数据项:组成数据元素、有独立含义、不可分割的最小单位
数据对象:性质相同数据元素的集合,是数据的一个子集
数据结构:相互存在一种或多种特定关系的数据元素的集合
逻辑结构:从逻辑关系上描述数据,它与数据的存储无关,是独立于计算机的
逻辑结构的两个要素:数据元素和关系
逻辑结构的四类基本结构,集合结构、线性结构、树结构、图结构或网状结构
抽象数据类型:由用户定义的、表示应用问题的数学模型以及定义在这个模型上的一组操作的总称
抽象数据类型的三大部分:数据对象、数据对象上的关系集合、数据对象的基本操作集合
顺序存储结构,借助元素在存储器中的相对位置来表示数据元素之间的逻辑关系
链式存储结构,为表示节点之间的关系,给每个节点附加指针字段,用于存放后继元素的存储地址
算法的五个重要特性,有穷性、确定性、可行性、输入、输出
算法的四个评判标准,正确性、可读性、健壮性、高效性
衡量算法效率的方法:事后估计法、事前分析估计法
不考虑计算机的软硬件等环境因素,影响算法时间代价的最主要因素是问题规模
时间复杂度,logn 算法的时间复杂度与问题的规模有关,还与待处理数据的初态有关 顺序表是随机存储的结构,链表是顺序存储的结构 顺序表的初始化 Status InitList(SqList &L){ L.elem = new ElemType[MaxSize]; if(!L) exit(OVERFLOW); L.length = 0; } 取值 Status GetElem(SqList L,int i, ElemType &e){ if(i<1||i>L.length) return ERROR; e = L.elem[i-1]; return OK; } 查找 int LocateElem(SqList L,ElemType e){ for(i=0;i if(L.elem[i] == e) return i+1; } return 0; } 插入 Status ListInsert(SqList &L,int i,ElemType e){ if(i<1 || i>L.length+1) return ERROR; if(L.length == MaxSize) return ERROR; for( j = L.length-1; j>=i-1;j--){ L.elem[j+1] = L.elem[j]; } L.elem[i-1] = e; L.length++; return OK; } 平均时间复杂度:2/n 删除 Status ListDelete(SqList &L,int i){ if(i<1 || i>L.length) return ERROR; for(j=i;j L.elem[j-1] = L.elem[j]; } L.length--; return OK; } 平均时间复杂度:n-1/2 长度为N的顺序表中,插入一个新元素平均需要移动表中N/2个元素,删除一个元素平均需要移动N-1/2个元素 链表的初始化 Status InitList(LinkList &L){ L = new LNode; L->next = null; return OK; } 取值 Status GetElem(LinkList L,int i,ElemType &e){ p = L->next;j=1; while(p&&j
p = p->next; j++; } if(!p || j>i) return ERROR; e = p ->data; return OK; } 查找 LNode *LocateElem(LinkList L,ElemType e){ p = L->next;j = 0; while(p&&p->data!=e){ p= p->next; } return p; } 插入 Status ListInsert(LinkList &L,int i,ElemType e){ p = L; j = 0; while(p&&j p = p->next; j++; } if(!p || j>i-1) return ERROR; s = new LNode; s->data = e; s- >next = p - >next; p->next = s; return OK; } 时间复杂度:O(n) 删除 Status ListDelete(LinkList &L,int i){ p =L; j = 0; while(p->next&&j p = p->next; j++; } if(!p || j>i-1) return ERROR; q = p->next; p->next = q->next; delete q; return OK; } 时间复杂度 :O(n) 插入与删除的循环条件不同,是因为插入的合法位置有n+1个,删除的合法位置有n个,如果删除的循环条件与插入一致,会出现空指针的情况 前插法创建单列表 头结点与首元结点之间 void CreateList_H(LinkList &L,int n){ L = new LNode; L->next = null; for(i=0;i p = new LNode; cin>>p->data; p ->next = L->next; L->next = p; } } 后插法创建单链表 所有节点之后 void CreateList_R(LinkList &L,int n){ L = new LNode; L ->next = null; r = L; for(i = 0;i cin>>p->data; p->next = null; r ->next = p; r = p; } } 顺序表的存储密度为1,链表的存储密度小于1。 进行存取操作考虑顺序表,进行插删操作考虑链表。 顺序表的合并 void MergeList_Sq(SqList LA,SqList LB,SqList &LC){ LC.length = LA.length+LB.length; LC = new ElemType[LC.length]; pc =LC.elem, pa = LA.elem, pb = LB.elem; pa_last = LA.elem+LA.length-1; pb_last = LB.elem+LB.length-1; while(pa<=pa_last && pb<=pb_last){ if(*pa<=*pb){ *pc++=*pa++; } else *pc++=*pb; } while(pa<=pa_last){*pc++=*pa;} whilel(pb<=pb_last){*pc++=*pb;} } 链表的合并 void MergeList_L(LinkList &LA, LinkList &LB, LinkList &LC){ LC = LA;//直接合并到LA后面 pc = LC, pa = LA->next, pb = LB->next; while(pa&&pb){ if(pa->data<=pb->data){ pc->next = pa, pc = pa, pa=pa->next} else pc->next = pb, pc = pb, pb=pb->next; pc ->next = pa?pa:pb; delete LB;要把LB的头结点删掉 } } 比较项目 链表 存储空间 预先分配,容易造成空间闲置或溢出 存储密度 等于1 为表示节点之间的逻辑关系而增加额外的存储开销,小于1 存储元素 随机存储,访问时间复杂度O(1) 顺序存储,访问时间复杂度O(n) 插入、删除 时间复杂度O(n) 时间复杂度O(1) 使用情况 长度变化不大,不经常使用插删操作 长度变化较大,经常进行插删操作 顺序栈的初始化 Status InitStack(SqStack &S){ S.base = new SElemType[MaxSize]; if(!S.base) exit(OVERFLOW); S.top = S.base; S.stackSize = MaxSize; return OK; } 入栈 Status Push(SqStack &S, SElemType e){ if(S.top-S.base==MaxSize) return ERROR; *S.top = e; S.top++; return OK; } 出栈 Status Pop(SqStack &S,SElemType &e){ if(S.top==S.base) return ERROR; e = *--S.top; return OK; } 取栈顶 SElemType GetTop(SqStack S, SElemType &e){ if(S.base!=S.top){ return *(S.top-1);} } 链栈的初始化 Status InitStack(LinkStack &S){ S = null; return OK; } 入栈 头插,连接到栈顶上方,且链栈没有设置栈顶与栈底指针 Status Push(LinkStack &S, SElemType e){ p = new StackNode; p ->data = e; p->next = S; S = p; return OK; } 出栈 Status Pop(LinkStack &S , SElemType &e){ if(S==null) return ERROR; e = S->data; p = S; S = S->next; delete p; return OK; } 取栈顶 SElemType GetTop(LinkStack S, SelemType &e){ if(S!=null) return S->data; } 汉诺塔算法(要能够掌握递归的过程,有考题会让画递归调用图) void move(char A,int n,char C){ count< } void Hanoi(int n, char A,char B, char C){ if(n==1) move(A,1,C); else{ Hanoi(n-1,A,C,B); move(n,A,B,C); Hanoi(n-1,B,A,C); } } 顺序队列的初始化 队首与队尾都是整数 Status InitQueue(SqQueue &Q){ Q.base = new QElemType[MaxSize]; if(!Q.base) exit(OVERFLOW); Q.front = Q.rear = 0; return OK; } 入队 对于循环队列的队首与队尾指针改变都需要对队长取余 Status EnQueue(SqQueue &Q, QElemType e){ if((Q.rear+1%MaxSize)==Q.froznt) return ERROR; Q.base[Q.rear] = e; Q.rear=(Q.rear+1)%MaxSize; return OK; } 出队 Status DeQueue(SqQueue &Q,QElemType &e){ if(Q.rear == Q.front) return ERROR; e = Q.base[Q.front]; Q.front = (Q.front+1)%MaxSize; return OK; } 取队首 QElemType GetHead(SqQueue Q,QElemType &e){ if(Q.front!=Q.rear){ return Q.base[Q.front]; } } 链式队列的初始化 front不存储,入栈直接连接到队尾后 Status InitQueue{ Q.front = Q.rear = new QNode; Q.front ->next =null; return OK; } 入队 尾插 Status EnQueue(LinkQueue &Q, QElemType e){ p = new QNode; p->data = e; p->next = null; Q.rear->next = p; Q.rear = p; return OK; } 出队 Status DeQueue(LinkQueue &Q, QElemType &e){ if(Q.rear==Q.front) return ERROR; p = Q.front->next; e = p->data; Q.front->next = p->next; if(Q.rear = p) Q.rear = Q.front;//删除了最后一个元素 delete p; return OK; } 取队首 QElemType GetHead(LinkQueue Q, QElemType &e){ if(Q.rear!=Q.front) { return Q.front->next->data; } } 进制转换 void converion(int N){ InitStack(S); while(N){ Push(S,N%8); N/=8; } while(!StackEmpty(S)){ Pop(S,e); count< } } 括号匹配 Status Matching(){ InitStack(S); flag = 1; cin>>ch; while(ch!='#'&&flag){ Swith(ch){ case '[': case '{': Push(S,ch); break; case ']': if(!StackEmpty(S)&&GetTop(S)=='['){ Pop(S,e); } else flag = 0; break; case '}': if(!StackEmpty(S)&&GetTop(S)=='}'){ Pop(S,e); } else flag = 0; break; } } if(flag&&StackEmpty(S)) return true; else return false; } 队列应用于打印机、模拟排队场景 堆可以为每一个新产生的串动态分配一块实际串长需要的存储空间 模式匹配算法 BF算法(牢记i指针和i指针的回溯位置) int Index_BF(SString S, SString T, int pos){ i = pos, j =1; while(i<=S.length && j<=T.length){ if(S.ch[i]==T.ch[j]) i++, j++; else{ i = i-j+2; j = 1;//匹配失败,重新追溯 } } if(j>T.length) return i-T.length;//子串匹配完成,成功 else return 0; } 最好情况时间复杂度,O(n+m) 最坏时间复杂度,O(n*m) 时间复杂度,O(m*n) 求子串的个数=n*(n+1)/2+1 低下标优先优先存储类似行优先存储,高下标优先存储类似于列优先存储 对称矩阵,以行优先为例(具体考虑是从1开始存储还是0开始存储,0开始存储需要减1,1开始存储不需要减1) i*(i-1)/2+j-1 i>=j 下三角 j*(j-1)/2+j-1 i 上三角 三角矩阵,从1开始 (i-1)*(2n-i+1)/2+(j-i) i<=j 上三角矩阵 0或n*(n+1)/2 i>j 上三角矩阵 i*(i-1)/2+j-1 i>=j 下三角矩阵 0或n*(n+1)/2 i 下三角矩阵 广义表的长度是指表中子表个数,深度指子表中元素的最大个数 广义表的表头可能是一个单原子,也可能是一个子表;表尾一定是一个广义表 计算规则(牢记),根据节点数计算二叉树有多少种=(1/n+1)*C2n n 二叉树的第n层最多为2^(n-1)个,深度为k的二叉树至多有(2^k)-1个节点,证明过程 总节点数N=n0+n1+n2,其中度0节点比度2节点多一个,即n0=n2+1,所以N=2*n2+n1+1,证明过程 具有n个节点的完全二叉树的深度为(log 2 n)+1,证明过程 顺序存储结构仅适用于完全二叉树,对于一般的二叉树采用的是链式存储方式 含有n个节点的二叉链表中有n+1个空链域 中序遍历 void InOrderTraverse(BiTree T){ if(T){ InOrderTraverse(T->lchild); count< InOrderTraverse(T->rchild); } } 中序遍历,非递归算法 void InOrderTraverse(BiTree T){ InitStack(S),p = T; q = new BitNode;//用于暂时存放栈顶弹出的元素 while(p || !StackEmpty(S)){ if(p){ Push(S,p); p = p->lchild; } else{ Pop(S,q); count< p = q->rchild;//访问右子树 } } } 递归与非递归遍历二叉树,无论采用哪一种次序进行遍历,含n个节点的二叉树,其时间复杂度为O(n),空间复杂度也为O(n) 二叉树的先序与中序,或中序与后序能唯一地确定一颗二叉树 先序创建二叉链表,递归 void CreateBiTree(BiTree T){ cin>>ch; if(ch=='#'){ T = NULL; } else{ p = new BiTNode; p->data = ch; CreateBiTree(T->lchild); CreateBiTree(T->rchild); } } 复制二叉树,递归 void Copy(BiTree T, BiTree &NewT){ if(!T){ NewT = NULL; return; } esle{ NewT = new BiTNode; NewT->data = T->data; Copy(T->lchild,NewT->lchild); Copy(T->rchild,NewT->rchild); } } 计算深度,递归 int Depth(BiTree T){ if(T==NULL) return 0; else{ m = Depth(T->lchild); n = Depth(T->rchild); if(m>n) return (m+1); else return (n+1); } } 统计各节点个数 int NodeCount(BiTree T){ if(T==NULL) return 0; else{ return NodeCount(T->lchild)+NodeCount(T->rchild)+1; } } int SingleNode(BiTree T){ if(T==NULL) return 0; if(T->lchild==NULL&&T->rchild!=NULL || T->lchild!=NULL&&T->rchild==NULL) return 1; else{ m = SingleNode(T->lchild); n = SingleNode(T->rchild); num = m+n; } return num; } int LeafNode(BiTree T){ if(T==NULL) return 0; if(T->lchild==NULL && T->rchild==NULL) return 1; else{ m = LeafNode(T->lchild); n = LeafNode(T->rchild); num = m + n; } return num; } int DoubleNode(BiTree T){ if(T==NULL || (!T->rchild && !T->lchild)) return 0; else{ if(T->rchild && T->rchild){ num++;//只要不是空或者、单分支、度0节点 num+ =DoubleNode(T->lchild); num+ =DoubleNode(T->rchild); } } return num; } 树的先根和后根序列遍历依次对应先序与中序遍历;森林的先序和中序遍历依次对应先序与中序遍历 构造哈夫曼树,n个叶节点的哈夫曼树共有2n-1个节点 初始化:首先申请2n个单元,将所有单元的双亲,左孩子,右孩子的下标初始化为0,最后循环n次输入前n个单元的叶子的权重; 创建:通过n-1次的选择、删除、合并 选择是选择双亲为且权重最小的两个树根节点s1和s2; 删除是将s1,s2的双亲改为非0; 合并是将s1,s2的权值之和作为一个新的节点存入数组n+1号及之后的单元中,同时记录这个节点的左孩子与右孩子是s1,s2 void CreateHuffmanTree(HuffmanTree &HT, int n){ if(n<=1) return; m = 2*n-1; HT = new HTNode[m+1];//0号单元未使用 for(i = 1;i<=m;i++){HT[i].parent = 0, HT[i].lchild = 0, HT[i].rchild = 0;} for(i = 1;i<=n;i++){cin>>HT[i].weight;} for(i = n+1;i<=m;i++){ Select(HT,i-1,s1,s2); HT[s1].parent = i, HT[s2].parent = i; HT[i].lchild = s1, HT[i].rchild = s2; HT[i].weight = HT[s1].weight+HT[s2].weight; } } 哈夫曼编码是前缀编码,是最优前缀编码 所有点的度数和 = 2*边数和 n个顶点的无向图最多有 n*(n-1)/2条边 n个顶点的有向图·最多有 n*(n-1)条边 连通图的连通分支量为 0 有向树,有一个顶点入度为,其余顶点的入度均为1的有向图 邻接矩阵的存储结构 typedef struct{ VerTexType vexs[MVNum];//顶点表 ArcType arcs[MVNum][MVNum];//邻接矩阵 int vexnum,arcnum;//顶点数,边数 }AMGraph; 邻接矩阵表示法创建无向网 Status CreateUDN(AMGraph G){ cin>>G.vexnum>>G.arcnum; for(i=0;i cin>>G.vexs[i]; } for(i=0;i for(j=0;j G.arcs[i][j] = MaxInt; } } for(k=0;k cin>>v1>>v2>>w; i = LocateVex(G,v1), j = LocateVex(G,v2); G.arcs[i][j] = w;// 如果是图,只需要赋值为1 G.arcs[j][i] = G.arcs[i][j];//如果是有向,则不需要对称复制 } return OK; } 对于无向图,第i行就是Vi的度;对于有向图,第i行是Vi的出度,第i列是Vi的入度 一个图的邻接图唯一,但邻接表不唯一,邻接表中各节点的链接次序取决于邻接表的算法还有边的输入顺序 邻接矩阵表示的优缺点(对比着记忆0) 优点 便于判断两个顶点之间是否有边;便于计算各顶点的度 缺点 不便于增加与删除顶点;不便于统计边的条数;空间复杂度更高 邻接表表示的优缺点 优点 便于增加和删除顶点;便于统计边的条数;空间利用率更高 缺点 不便于判断顶点之间是否有边;不便于计算顶点的度数 深度优先搜索——树的先序遍历的推广,是一个递归的过程,借助栈 采用邻接矩阵,深度优先搜索遍历 void DFS(Graph G, int v){ visited[v] = true; cout< for(w = 0;w if(G.arcs[v][w]!=0 && G.arcs[v][w]!=MaxInt && !visited[w]){ DFS(G,w); } } } 采用邻接表,深度优先搜索遍历 void DFS(ALGraph G, int v){ visited[v] = true; cout< for(p = G.vertices[v].firstarc;p;p=p->nextarc){ w = p->adjvex; if(!visited[w]) DFS(G,w); } } 广度优先遍历——树的层次遍历,借助队列 以邻接表为例 void BFS(Graph G, int v){ cout< InitQueue(Q); EnQueue(Q,v); while(!QueueEmpty(Q)){ DeQueue(Q,u); for(p = G.vertices[u].firstarc;p;p=p->nextarc){ w = p->adjvex; if(!visited[w]){ cout< visited[w] = true; EnQueue(Q,w); } } } } 深度优先搜索和广度优先搜索的时间复杂度一样,当用邻接矩阵存储时,时间复杂度为O(n^2);采用邻接表存储的时候,时间复杂度是O(n+e),两种遍历的不同在于对顶点的访问顺序不同 图的应用 最小生成树,各边代价之和最小的那颗生成树 prim算法——加点法 时间复杂度O(n^2),与边数无关,空间复杂度O(n),适用于求稠密图的最小生成树 kruscal算法——加边法 时间复杂度O(elog2e),与边数有关,适用于求稀疏图的最小生成树 最短路径 Dijkstra算法 时间复杂度O(n^2) Floyd算法 时间复杂度O(n^3),空间复杂度O(n^2) 拓扑排序 AOV-网,以顶点表示活动,用弧表示活动间的优先关系的有向图 选择无前驱的顶点,删除该顶点以及与它有联系的弧,在剩下的节点重复操作,输出的顶点如果少于总顶点数,则说明有环,否则没有环 对于给出的AOV网判断网中是否存在环,对有向图的所有顶点进行拓扑排序,如果所有的顶点都在拓扑序列中,那么不存在环 时间复杂度O(n+e) 关键路径,源点到汇点的带权路径长度最长的路径 AOE-网,以边表示活动的网 时间复杂度O(n+e) 查找(记忆各个查找算法的特点) 顺序查找 折半查找 分块查找 二叉排序树查找 对表结构没有要求,使用顺序,也使用链式 比较次数少,查找效率高 在表中进行插删操作时,只需要找到对应的块就可以在块内进行插删操作,如果线性表经常动态变化,又需要快速查找,则使用分块查找 对于经常插入,删除,和查找运算的表,采用二叉排序树更好 缺点 平均查找长度较大,查找效率低 对结构要求高,只能适用于顺序存储结构的有序表,不适用数据元素经常变动的线性表 要增加一个索引表的存储空间并对初始索引表进行排序运算 平均查找长度 时间复杂度 (n+1)/2 O(n) 最坏情况查找不超过log2n+1 与二叉排序树的一样 O(log2n) 1/n * (每层个数*该层层数) O(log2n) 顺序查找 平均查找长度ASL = 1/n * (n*(n+1)/2),时间复杂度O(n) 优点 对表结构没有要求,使用顺序,也使用链式 缺点 平均查找长度较大,查找效率低 折半查找 要求线性表必须采用顺序存储结构,最好情况查找1次,最坏情况查找log2(n)+1次,平均查找长度ASL=log2(n+1)-1 优点 比较次数少,查找效率高 缺点 对结构要求高,只能适用于顺序存储结构的有序表,不适用数据元素经常变动的线性表 分块查找 优点 在表中进行插删操作时,只需要找到对应的块就可以在块内进行插删操作,如果线性表经常动态变化,又需要快速查找,则使用分块查找 缺点 要增加一个索引表的存储空间并对初始索引表进行排序运算 二叉排序树的查找 含有n个节点的二叉排序树的平均查找长度和树的形态有关,最坏情况二叉树的形态为单分支形态,O(log2n) ASL = 1/n*(1+2+...+n) 对于经常插入,删除,和查找运算的表,采用二叉排序树更好 void SearchBST(BSTree T, KeyType key){ if(!T || key==T->data.key) return T; else if(key else return SearchBST(T->rchild,key); } 二叉排序树的插入 void InsertBST(BSTree &T, ElemType e){ if(!T){ s = new BSTNode; s->data = e; s->lchild=NULL, s->rchild = NULL; T = s; } else if(e.key else if(e.key>T->data.key) return InsetBST(T->rchild,e); } 时间复杂度同查找一样,O(log2n) 二叉排序树的创建 void CreateBST(BSTree &T){ //依次读入关键字为key的节点,把相应的节点插入到二叉排序树中 T= NULL; cin>>e; while(e.key!='#'){//输入结束标志 InsetBST(T,e); cin>>e; } } 插入一个节点的时间复杂度是O(log2n),n个节点的时间复杂度是O(nlog2n) 二叉排序树的删除 基本过程也是查找,所以时间复杂度是O(log2n),采用的过程是找到待删节点的直接前驱节点p,将p代替待删节点,将p的左子树接到p的双亲的右子树上面 平衡二叉树,AVL树 左子树与右子树的深度之差绝对值不超过1;左子树与右子树也是平衡二叉树 深度与log2n是同一数量级,所以查找的时间复杂度O(log2n) 散列表查找法,无需作比较或做很少比较,按照这种关系直接由关键字找到相对应的记录 散列函数和散列地址,在记录的存储地址p和关键字key之间建立一个确定的对应关系H,使得p=H(key),称这个对应关系H是散列函数,p是散列地址 散列表,一个连续的地址空间,用以存储按照散列函数计算得到的相应的散列地址的数据记录 冲突与同义词,对不同的关键字可能得到同一个散列地址,这种现象叫做冲突;具有相同函数值的关键字对该散列函数而言是同义词 处理冲突的方法,开放地址法,链地址法 散列函数的构造方法,考虑因素 散列表的长度 关键字的长度 关键字的分布情况 计算散列函数所需的时间 记录的查找频率 装填因子α越小,冲突发生频率越小,反之越大;散列函数均匀的情况下,影响平均查找长度的因素——处理冲突的方法和装填因子α,而不是待存放元素个数 散列表平均查找长度 插入排序(看后面的对比) 直接插入排序,时间复杂度O(n^2),空间复杂度,需要借助一个监视哨,O(1) 算法稳定、简单,适用于链式存储结构,顺序存储结构;更适用于初始记录基本有序的情况,初始记录无序,n较大时,则不适用 void InsertSort(SqList &L){ for(i=2;i<=L.length;i++){ if(L.r[i].key L.r[0].key = L.r[i].key; L.r[i] = L.r[i-1]; for(j=i-2; L.r[0].key L.r[j+1] = L.r[j]; } L.r[j+1] = L.r[0];//比较到比插入的值还要小的数时,又往前面走了一个单位,所以要j+1 } } } 折半插入排序 时间复杂度O(n^2),空间复杂度O(1);只适用于顺序结构,不适用链式结构,适用于初始无序,n值较大的情况 void BInsertSort(SqList &L){ for(i = 2;i<=L.length;i++){ L.r[0] = L.r[i]; low = 1, high = i-1; while(low<=high){ m = (low+high)/2; if(L.r[0].key else low = m+1; } for(j=i-1;j>=high+1;j--) L.r[j+1] = L.r[j];//后移 L.r[high+1] = L.r[0];//替换 } } 希尔排序 记录跳跃式地移动导致排序是不稳定的,只能适用于顺序结构,不能使用链式结构,适用于初始记录无序,n值较大的情况 冒泡排序 void BubbleSort(SqList &L){ m = L.length-1,flag = 1; while(m>0 && flag==1){ flag = 0;//默认本趟没有发生了交换 for(j = 1;j<=L.length;j++){ if(L.r[j+1] flag = 1;//发生了交换做的标志 temp = L.r[j+1]; L.r[j+1] = L.r[j]; L.r[j] = temp; } } m--; } } 时间复杂度O(n^2),空间复杂度O(1);稳定排序,同样适用于链式结构,移动次数多,比直接插入排序差;不适用初始无序,n值较大的情况 快速排序 int Partition(SqList &L, int low, int high){ L.r[0] = L.r[low]; pivotkey = L.r[low].key; while(low while(low L.r[low] = L.r[high]; while(low L.r[high] = L.r[low]; } //枢轴变量到位 L.r[low] =L.r[0]; return low; } void QSort(SqList &L, int low, int high){ //调整low=1, high = L.length if(low pivotloc = Partition(L,low,high); QSort(L,low,pivotloc-1); QSort(L,pivotloc+1,high); } } void QuickSort(SqList &L){ QSort(L,1,L.length); } 时间复杂度O(nlog2n),空间复杂度,因为调用是递归的,需要使用工作栈,O(n);适用于顺序结构,很难适用链式结构、适用初始记录无序,n较大的情况 选择排序、一次选择最小的记录 void SelectSort(SqList &L){ for(i=1;i k =i; for(j =i+1;j<=L.length;j++){ if(L.r[j].key } if(k!=i){ temp = L.r[i]; L.r[i] = L.r[k]; L.r[k] = temp; } } } 时间复杂度O(n^2),空间复杂度O(1)、算法不稳定、可适用于链式结构、顺序结构 堆排序 不稳定排序,只适用顺序结构,不适用链式结构; 二路归并排序 时间复杂度O(nlog2n),空间复杂度O(n),是稳定排序,适用顺序结构、链式结构,工作时仍然需要开辟工作栈 (要掌握每个算法走一趟后的序列,不需要掌握具体的算法,自己会一两个就行,重要的是排序算法的过程,还有各自的特性) 时间复杂度O(n^2),空间复杂度O(1) 适用于初始记录有序的情况,不适用初始记录无序,n较大的情况 适用顺序结构,也适用链式结构 稳定 折半插入排序 时间复杂度O(n^2),空间复杂度O(1);适用初始无序,n较大的情况;适用顺序结构,不适用链式结构 稳定 希尔排序 时间复杂度O(n^1.3),空间复杂度O(1) 适用于初始记录无序,n较大的情况 只适用顺序结构,不适用链式结构 不稳定 冒泡排序 时间复杂度O(n^2),空间复杂度O(1) 不适用初始记录有无序,n较大的情况 适用顺序结构、链式结构 稳定 快速排序 时间复杂度O(nlog2n),空间复杂度O(n) 适用初始记录无序,n较大的情况 适用顺序结构,很难适用链式结构 不稳定 选择排序 时间复杂度O(n^2),空间复杂度O(1) 适用顺序结构、链式结构 堆排序 时间复杂度O(nlog2n),空间复杂度O(1) 适用于n较大的情况 只能使用顺序结构,不适用链式结构 不稳定 二路归并排序 时间复杂度O(nlog2n),空间复杂度O(n),需要借助工作栈 适用顺序结构、链式结构 稳定
顺序表
动态分配,不会溢出或闲置
优点
直接插入排序
稳定