线性表

顺序表

定义

  1. //静态分配
  2. #define MaxSize 50 //最大长度
  3. typedef struct {
  4. ElemType data[MaxSize];//元素
  5. int length;//长度
  6. }SqList;
  1. //动态分配
  2. #define InitSize 100 //表长度初始定义
  3. typedef struct{
  4. ElemType *data;
  5. int MaxSize,length;
  6. }SeqList;
  7. //初始动态分配
  8. L.data=(ElemType *)malloc(sizeof(InitSize));

动态分配不是链式存储,它同样也是顺序存储,物理结构没有发生变化,依然是随机存取。

基本操作

  1. //线性表的基本操作
  2. InitList(&L);//初始化表,构造一个空表
  3. Length(L);//求表长,返回L的长度
  4. LocateElem(L,e);//按值查找操作
  5. GetElem(L,i);//按位查找,获取第i个位置的元素值
  6. ListInsert(&L,i,e);//插入操作,在L的第i个位置插入指定元素e
  7. ListDelete(&L,i,&e);//删除操作,删除第i个位置上元素,并用e返回删除元素值
  8. PrintList(L);//输出操作
  9. Empty(L);//判空操作
  10. DestroyList(&L);//销毁操作,释放占用内存空间

链表

单链表结点定义

  1. typedef struct LNode{
  2. ElemType data;//数据域
  3. struct LNode *next;//指针域
  4. }LNode,*LinkList;

双链表结点定义

  1. typedef struct DLNode{
  2. ElemType data;
  3. struct DLNode *prior;//指向前驱结点
  4. struct DLNode *next;//指向后继结点
  5. }DLNode,*DLinkList;

单链表基本操作

建立单链表

  1. //头插法建立单链表,逆向建立单链表
  2. LinkList List_HeadInsert(LinkList &L){
  3. LNode *s;
  4. int x;
  5. L=(LinkList)malloc(sizeof(LNode));//创建头结点
  6. L->next=NULL;
  7. scanf("%d",&x);
  8. while(x!=111111){
  9. s=(LNode*)maloc(sizeof(LNode));//创建新结点
  10. s->data=x;
  11. s->next=L->next;//每次将新结点插入到头结点后面
  12. L->next=s;
  13. scanf("%d",&x);
  14. }
  15. return L;
  16. }
  1. //尾插法建立单链表,正向建立单链表
  2. LinkList List_TailInsert(LinkList &L){
  3. int x;
  4. L=(LinkList)malloc(sizeof(LNode));
  5. LNode *s,*r=L;
  6. scanf("%d",&x);
  7. while(x!=1111){
  8. s=(LNode*)malloc(sizeof(LNode));
  9. s->data=x;
  10. r->next=s;
  11. r=s;
  12. scanf("%d",&x);
  13. }
  14. r->next=NULL;//尾结点指针置空
  15. return L;
  16. }

按序号查找结点值

  1. LNode *GetElem(LinkList L,int i){
  2. int count=1;
  3. LNode *p=L->next;
  4. if(i==0)
  5. return L;
  6. if(i<1)
  7. return NULL;
  8. while(p&&count<i){
  9. p=p->next;
  10. count++;
  11. }
  12. return p;
  13. }

按值查找表结点

  1. LNode *LocateElem(LinkList L,ElemType e){
  2. LNode *p=L->next;
  3. while(p->data!=e&&p!=NULL)
  4. p=p->next;
  5. return p;
  6. }

前插结点

  1. p=GetElem(L,i-1);
  2. s->next=p->next;
  3. p->next=s;

后插操作实现结点的前插

  1. //将*s结点插入到*p结点之前
  2. s->next=p->next;
  3. p->next=s;
  4. temp=p->data;
  5. p->data=s->data;
  6. s->data=temp;

删除结点

  1. //找前驱实现删除结点
  2. p=GetElem(L,i-1);
  3. q=p->next;//q指向被删除结点
  4. p->next=q->next;
  5. free(q);
  6. //通过删除后继来删除结点
  7. q=p->next;
  8. p->data=q->data;//与后继结点交换数据域
  9. p->next=q->next;
  10. free(q);

双链表

尾插法建立双链表

  1. void createDLinkList(DLinkList &L){
  2. DLNode *s,*r;
  3. int x;
  4. L=(DLinkList)malloc(sizeof(DLNode));
  5. L->prior=NULL;
  6. L->next=NULL;
  7. r=L;
  8. scanf("%d",&x);
  9. while(x!=1111){
  10. s=(DLNode*)malloc(sizeof(DLNode));
  11. s->data=x;
  12. r->next=s;
  13. s->prior=r;
  14. r=s;
  15. scanf("%d",&x);
  16. }
  17. r->next=NULL;
  18. return L;
  19. }

双链表按值找结点

  1. DLNode *find_x(DLinkList L,ElemType x){
  2. DLNode *p=L->next;
  3. while(p!=NULL){
  4. if(p->data==x)
  5. break;
  6. p=p->next;
  7. }
  8. return p;
  9. }

双链表插入结点

  1. //p结点之后插入s
  2. s->next=p->next;
  3. s->prior=p;
  4. p->next=s;
  5. s->next->prior=s;

双链表删除结点

  1. //删除p结点的后继结点
  2. q=p->next;
  3. p->next=q->next;
  4. q->next->prior=p;
  5. free(q);

循环链表

循环单链表和单链表区别:最后一个结点的指针不是NULL,而改为指向头结点;
循环双链表,头结点的prior指针还要指向表尾结点

静态链表

借助数组来描述线性表的链式存储,结点也有数据域和指针域.这里的指针是结点的相对地址(数组下标),又称游标,以next==-1作为结束的标志

  1. #define MaxSize 50
  2. typedef struct{
  3. ElemType data;//
  4. int next;//下一个元素的数组下标
  5. }SLinkList[MaxSize];

栈和队列是受限的线性表。(不可以随便读取栈或队列中间的某个数据)

栈的基本操作

入栈:先移动指针,再进栈。Stack[++top]=x
出栈:先取出元素,再移动指针。x=Stack[top—];

  1. InitStack(&S);//初始化一个空栈,S.top=-1;
  2. StackEmpty(S);//判栈空
  3. Push(&S,x);//进栈,将x加入栈顶(栈未满情况下)
  4. Pop(&S,&x);//出栈,弹出栈顶元素,并用x返回(栈非空的情况下)
  5. GetTop(S,&x);//读栈顶元素,并用x返回(栈非空情况下)
  6. DestoryStack(&S);//销毁栈,并释放栈S占用的存储空间


顺序栈

  1. #define MaxSize 50
  2. typedef struct{
  3. ElemType data[MaxSize];//存放栈中元素
  4. int top;//栈顶指针
  5. }SqStack;

链栈

  1. typedef struct Linknode{
  2. ElemType data;//数据域
  3. struct Linknode *next;//指针域
  4. }*LiStack;

队列(Queue)

队列的基本操作

  1. InitQueue(&Q);//初始化队列,构造一个空队列Q
  2. QueueEmpty(Q);//判队列空
  3. EnQueue(&Q,x);//入队,队列未满则将x加入队尾
  4. DeQueue(&Q,&x);//出队,队列非空,删除队头元素,并用x返回
  5. GetHead(Q,&x);//读队头元素,若队列非空将队头元素赋值给x

顺序队列

  1. #define MaxSize 50//队列中最大元素个数
  2. typedef struct {
  3. ElemType data[MaxSize];
  4. int front,rear;//队头指针和队尾指针
  5. }SqQueue;

初始状态(队空条件):Q.front==Q.rear==0
进队操作:先送值到队尾,再将队尾指针加1
出队操作:先取队头元素,再将队头指针加1

循环队列的基本操作

初始化:Q.front=Q.rear=0
队空:Q.front=Q.rear
队满:(Q.rear+1)%MaxSize==Q.front
入队:Q.data[Q.rear]=x;Q.rear=(Q.rear+1)%MaxSize;
出队:x=Q.data[Q.front];Q.front=(Q.front+1)%MaxSize;

链式队列

  1. typedef struct{//链式队列结点
  2. ElemType data;
  3. struct LinkNode *next;
  4. }LinkNode;
  5. typedef struct{//链式队列
  6. LinkNode *front,*rear;
  7. }LinkQuene;

队空:Q.rear==NULL或者Q.front==NULL
入队:Q->rear->next=p;Q->rear=p;
出队:p=Q->front;Q->front=p->next;x=p->data;free(p);

串的基本操作

  1. StrAssign(&T,chars);//赋值操作。把串T赋值为chars。
  2. StrCopy(&T,S);//复制操作。由串S复制得到串T。
  3. StrEmpty(S);//判空操作。若S为空串,则返回TRUE,否则返回FALSE。
  4. StrLength(S);//求串长。返回串S的元素个数。
  5. ClearString(&S);//清空操作。将S清为空串。
  6. DestroyString(&S);//销毁串。将串S销毁(回收存储空间)。
  7. Concat(&T,S1,S2);//串联接。用T返回由S1和S2联接而成的新串
  8. SubString(&Sub,S,pos,len);//求子串。用Sub返回串S的第pos个字符起长度为len的子串。
  9. Index(S,T);//定位操作。若主串S中存在与串T值相同的子串,则返回它在主串S中第一次出现的位置;否则函数值为0。
  10. StrCompare(S,T);//比较操作。若S>T,则返回值>0;若S=T,则返回值=0;若S<T,则返回值<0。

定长顺序存储表示

  1. #define MAXLEN 255
  2. typedef struct{
  3. char ch[MAXLEN];
  4. int length;
  5. }SString;

变长顺序存储(堆分配存储表示)

  1. typedef struct{
  2. char *ch;
  3. int length;
  4. }HString;

串的模式匹配

子串的定位操作通常称为串的模式匹配,求的是子串(模式串)在主串中的位置

简单模式匹配(主串回溯)

  1. int Index(SString S,SString T){
  2. int i=1,j=1,k=i;//k记录检查子串的起始位置
  3. while(i<=S.length&&j<=T.length){
  4. if(S.ch[i]==T.ch[j]){
  5. ++i;
  6. ++j;
  7. }
  8. else{
  9. i=++k;//匹配失败,从主串的下一位置开始匹配
  10. j=1;
  11. }
  12. }
  13. if(j>T.length)
  14. return k;
  15. else
  16. return 0;
  17. }

KMP算法(主串不回溯)

next[j]的含义是:在子串的第j个字符与主串发生失配时,则跳到子串的next[j]位置重新与主串当前位置进行比较;next[1]=0,next[2]=1;

  1. //求next值
  2. void get_next(String T,int next[]){
  3. int i=1,j=0;
  4. next[1]=0;
  5. while(i<T.length){
  6. if(j==0||T.ch[i]==T.ch[j]){
  7. ++i;
  8. ++j;
  9. next[i]=j;
  10. }
  11. else
  12. j=next[j];
  13. }
  14. }
  15. //KMP算法
  16. int Index_KMP(String S,String T,int next[]){
  17. int i=1,j=1;
  18. while(i<=S.length&&j<=T.length){
  19. if(j==0||S.ch[i]==T.ch[j]){
  20. ++i;
  21. ++j;
  22. }
  23. else
  24. j=next[j];
  25. }
  26. if(j>T.length)
  27. return i-T.length;
  28. else
  29. return 0;
  30. }
  31. //KMP算法改进,修正next数组,使用nextval数组
  32. void get_nextval(String T,int nextval[]){
  33. int i=1,j=0;
  34. nextval[i]=0;
  35. while(i<T.length){
  36. if(j==0||T.ch[i]==T.ch[j]){
  37. ++i;
  38. ++j;
  39. if(T.ch[i]!=T.ch[j])
  40. nextval[i]=j;
  41. else
  42. nextval[i]=nextval[j];
  43. }
  44. else
  45. j=nextval[j];
  46. }
  47. }

树与二叉树(非常非常重要)

二叉树的链式存储

  1. typedef struct BiTNode{
  2. ElemType data;
  3. struct BiTNode *lchild,*rchild;//左右孩子指针
  4. }BiTNode,*BiTree;

线索二叉树的存储结构

ltag==0,表示lchild指向的是结点的左孩子,ltag==1,表示lchild指向的是结点的前驱;
rtag==0,表示rchild指向的是结点的右孩子,rtag==1,表示rchild指向的是结点的后继.

  1. typedef struct ThreadNode{
  2. ElemType data;
  3. struct ThreadNode *lchild,*rchild;
  4. int ltag,rtag;//左右线索标志
  5. }ThreadNode,*ThreadTree;

树的存储结构

双亲表示法

  1. #define MAX_Tree_Size 100
  2. typedef struct{
  3. ElemType data;
  4. int parent;//双亲位置域
  5. }PTNode;
  6. typedef struct{
  7. PTNode nodes[MAX_Tree_Size];//双亲表示
  8. int n;//结点数
  9. }PTree;

孩子兄弟表示法

  1. typedef struct CSNode{
  2. ElemType data;
  3. struct CSNode *firstchild,*nextsibing;//第一个孩子和右兄弟指针
  4. }CSNode,*CSTree;

*并查集存储结构和基本操作

通常用树或森林的双亲表示作为并查集的存储结构,每个子集合以一棵树表示。所有表示子集合的树,构成全集合的森林,存放在双亲表示数组内。通常用数组元素的下标代表元素名,用根结点的下标代表子集合名,根结点的双亲结点为负数。

  1. #define SIZE 100
  2. int UFSets[SIZE];//集合元素数组(双亲指针数组)
  3. //初始化,S即为并查集
  4. void Initial(int S[]){
  5. for(int i=0;i<SIZE;i++)
  6. s[i]=-1;
  7. }
  8. //Find操作(在并查集S中查找并返回包含元素x的根)
  9. int Find(int S[],int x){
  10. while(S[x]>=0)
  11. x=S[x];
  12. return x;//根的S[]小于0
  13. }
  14. //Union操作(求两个不相交子集合的并集)
  15. void Union(int S[],int Root1,int Root2){
  16. //要求Root1和Root2是不同的,且表示子集合的名字
  17. S[Root2]=Root1; //将根Root2链接到另一根Root1下面
  18. }

二叉树的遍历

先序遍历

根结点——>左子树——>右子树

递归

  1. void PreOrder(BiTree T){
  2. if(T!=NULL){
  3. visit(T);
  4. PreOrder(T->lchild);
  5. PreOrder(T->rchild);
  6. }
  7. }

非递归

  1. void PreOrder(BiTNode *bt){
  2. if(bt!=NULL){
  3. BiTNode *Stack[maxsize];//定义一个栈
  4. int top=-1;
  5. BiTNode *p;
  6. Stack[++top]=bt;//根结点入栈
  7. while(top!=-1){
  8. p=Stack[top--];
  9. visit(p);
  10. if(p->rchild!=NULL)//注意让右孩子先入栈
  11. Stack[++top]=p->rchild;
  12. if(p->lchild!=NULL)
  13. Stack[++top]=p->lchild;
  14. }
  15. }
  16. }

中序遍历

递归

  1. void InOrder(BiTree T){
  2. if(T!=NULL){
  3. InOrder(T->lchild);
  4. visit(T);
  5. InOrder(T->rchild);
  6. }
  7. }

非递归

  1. void InOrder(BiTNode *bt){
  2. if(bt!=NULL){
  3. BiTNode *Stack[maxsize];
  4. int top=-1;
  5. BiTNode *p;
  6. p=bt;
  7. while(top!=-1||p!=NULL){
  8. while(p!=NULL){
  9. Stack[++top]=p;
  10. p=p->lchild;
  11. }
  12. if(top!=-1){
  13. p=Stack[top--];
  14. visit(p);
  15. p=p->rchild;
  16. }
  17. }
  18. }
  19. }

后序遍历

递归

  1. void PostOrder(BiTree T){
  2. if(T!=NULL){
  3. PostOrder(T->lchild);
  4. PostOrder(T->rchild);
  5. visit(T);
  6. }
  7. }

非递归

  1. //两个栈实现
  2. void PostOrder(BiTNode *bt){
  3. if(bt!=NULL){
  4. BiTNode *Stack1[maxsize];
  5. int top=-1;
  6. BiTNode *Stack2[maxsize];
  7. int top2=-1;
  8. BiTNode *p=NULL;
  9. Stack1[++top1]=bt;
  10. while(top1!=-1){
  11. p=Stack1[top1--];
  12. Stack2[++top2]=p;//输出改为入Stack2
  13. if(p->lchild!=NULL)
  14. Stack1[++top1]=p->lchild;
  15. if(p->rchild!=NULL)
  16. Stack1[++top1]=p->rchild;
  17. }
  18. while(top2!=-1){
  19. //出栈序列即为后序遍历序列
  20. p=Stack2[top2--];
  21. visit(p);
  22. }
  23. }
  24. }
  25. //一个栈实现,在二叉树的存储结构中增加一个是否访问的tag域,初始为0
  26. void PostOrder(BiTree T){
  27. InitStack(S);
  28. p=T;
  29. while(p||!StackEmpty(S)){
  30. if(p){
  31. Push(S,p);
  32. p=p->lchild;
  33. }
  34. else{
  35. Pop(S,p);
  36. if(p->rchild&&(p->rchild).tag==0){
  37. Push(S,p);
  38. p=p->rchild;
  39. }
  40. else{
  41. visit(p);
  42. p.tag=1;
  43. p=NULL;
  44. }
  45. }
  46. }
  47. }

层次遍历

借助队列

  1. void LevelOrder(BiTree T){
  2. InitQueue(Q);
  3. BiTree p;
  4. EnQueue(Q,T);
  5. while(isEmpty(Q)){
  6. DeQueue(Q,p);
  7. visit(p);
  8. if(p->lchild!=NULL)
  9. EnQueue(Q,p->lchild);
  10. if(p->rchild!=NULL)
  11. EnQueue(Q,p->rchild);
  12. }
  13. }
  14. //定义循环队列,进行辅助操作
  15. void LevelOrder(BiTNode *bt){
  16. int front,rear;
  17. BTNode *Q[maxsize];
  18. front=rear=0;
  19. BTNode *p;
  20. if(bt!=NULL){
  21. rear=(rear+1)%maxsize;
  22. Q[rear]=bt;
  23. while(front!=rear){//队列非空
  24. front=(front+1)%maxsize;
  25. p=Q[front];
  26. visit(p);
  27. if(p->lchild!=NULL){
  28. rear=(rear+1)%maxsize;
  29. Q[rear]=p->lchild;
  30. }
  31. if(p->rchild!=NULL){
  32. rear=(rear+1)%maxsize;
  33. Q[rear]=p->rchild;
  34. }
  35. }
  36. }
  37. }

中序线索二叉树的构造和遍历

图(非常重要)

图的存储结构

邻接矩阵法

  1. #define MaxVertexNum 100 //顶点数目最大值
  2. typedef char VertexType;//顶点数据类型
  3. typedef int EdgeType;//带权图中边上权值的数据类型
  4. typedef struct{
  5. VertexType Vex[MaxVertexNum];//顶点表
  6. EdgeType Edge[MaxVertexNum][MaxVertexNum];//邻接矩阵,边表
  7. int vexnum,arcnum;//当前顶点数和弧数
  8. }MGraph;

邻接表法

  1. #define MaxVertexNum 100
  2. typedef struct ArcNode{ //边表结点
  3. int adjvex;//该弧所指向顶点的位置
  4. struct ArcNode *next;//指向下一条弧的指针
  5. InfoType info;//网的边权值
  6. }ArcNode;
  7. typedef struct VNode{ // 顶点表结点
  8. VertexType data;//顶点信息
  9. ArcNode *first;//指向第一条依附该顶点的弧的指针
  10. }VNode,AdjList[MaxVertexNum];
  11. typedef struct{
  12. AdjList vertices;//邻接表
  13. int vexnum,arcnum;//图的顶点数和弧数
  14. }ALGraph;

图的基本操作

  1. Adjacent(G,x,y);//判断图G是否存在边<x,y>
  2. Neighbors(G,x);//列出G中与结点x邻接的边
  3. InsertVertex(G,x);//在G中插入顶点x
  4. DeleteVertex(G,x);//从G中删除顶点x
  5. AddEdge(G,x,y);//向图G中添加<x,y>这条边
  6. RemoveEdge(G,x,y);//从图G中删除<x,y>这条边
  7. FirstNeighbor(G,x);//求G中顶点x的第一个邻接点,若有则返回顶点号,否则返回-1
  8. NextNeighbor(G,x,y);//返回除y后顶点x的下一个邻接点,若y是x的最后一个邻接点则返回-1
  9. Get_edge_value(G,x,y);//获取G中边<x,y>的权值
  10. Set_edge_value(G,x,y,v);//设置G中边<x,y>对应的权值为v

图的遍历

广度优先搜索

  1. bool visited[Max_Vertex_Num];//访问标记数组
  2. LinkQueue q;
  3. void BFSTrave(Graph G){
  4. for(int i = 0; i < G.vexnum;++i){
  5. visited[i] = false; //访问标记数组初始化
  6. }
  7. InitQueue(q); //初始化辅助队列Q
  8. for(int i = 0; i < G.vexnum; i++){ //从0个顶点开始遍历
  9. if(!visited[i]) //对每个连通分量调用一次BFS
  10. BFS(G,i); //vi未访问过,从vi开始BFS
  11. }
  12. }
  13. void BFS(Graph G,int v){ //从顶点v出发,广度优先遍历G
  14. visit(v); //访问初始顶点v
  15. visited[v] = true; //对v做已访问标记
  16. EnQueue(q,v); //顶点v入队列q
  17. while(!isEmpty(q)){
  18. DeQueue(q,v); //顶点v出队列q
  19. for(int w = FirstNeighbor(G,v); w >= 0; w = NextNeighbor(G,v,w)){
  20. //检测v所有邻接点
  21. if(!visited[w]){//w为v尚未访问的邻接顶点
  22. visit(w);
  23. visited[w] = true;//对w做已访问标记
  24. EnQueue(Q,w); //顶点w入队列
  25. }
  26. }
  27. }
  28. }

深度优先搜索

  1. bool visited[Max_Vertex_Num];
  2. void DFSTraverse(Graph G){
  3. for(int v = 0; v < G.vexnum; v++){
  4. visited[v] = false; //初始化已访问标记
  5. }
  6. for(int v = 0; v < G.vexnum; v++){
  7. if(!visited[v])
  8. DFS(G,v);
  9. }
  10. }
  11. void DFS(Graph G,int v){//从顶点v出发,深度优先遍历图G
  12. visit(v);
  13. visited[v] = true;
  14. for(int w = FirstNeighbor(G,v); w >= 0; w = NextNeighbor(G,v,w)){
  15. if(!visited[w]) //w为v尚未访问的邻接顶点
  16. visit(w);
  17. DFS(G,w);
  18. }
  19. }

图的应用

最小生成树

Prim算法

  1. void Prim(MGraph g,int v0,int &sum){
  2. int lowcost[maxsize],vset[maxsize],v;
  3. int i,j,k,min;
  4. v=v0;
  5. for(i=0;i<g.vexnum;++i){
  6. lowcost[i]=g.edges[v0][i];
  7. vset[i]=0;
  8. }
  9. vset[i]=1;//将v0并入树中
  10. sum=0;//累计权值
  11. for(i=0;i<g.vexnum-1;++i){
  12. min=INF;//INF为一个已经定义的比图中所有边权值都大的常量
  13. //选出侯选边中的最小者
  14. for(j=0;j<g.vexnum;++j){
  15. if(vset[j]==0&&lowcost[j]<min){
  16. min=lowcost[j];
  17. k=j;
  18. }
  19. vset[k]=1;
  20. v=k;
  21. sum+=min;
  22. //以刚并入的顶点更新侯选边
  23. for(j=0;j<g.vexnum;++j){
  24. if(vset[j]==0&&g.edges[v][j]<lowcost[j])
  25. lowcost[j]=g.edges[v][j];
  26. }
  27. }
  28. }

Kruskal算法

  1. typedef struct{
  2. int a,b;//一条边相连的两个顶点
  3. int w;//边的权值
  4. }Road;
  5. Road road[maxsize];
  6. int v[maxsize];//定义并查集数组
  7. int getRoot(int a){
  8. while(a!=v[a])
  9. a=v[a];
  10. return a;
  11. }
  12. void Kruskal(MGraph g,int &sum,Road road[]){
  13. int i;
  14. int N,E,a,b;
  15. N=g.vexnum;
  16. E=g.arcnum;
  17. sum=0;
  18. for(i=0;i<N;++i)
  19. sort(road,E);//对road数组的边按权值从小到大排列
  20. for(i=0;i<E;++i){
  21. a=getRoot(road[i].a);
  22. b=getRoot(road[i].b);
  23. if(a!=b){
  24. v[a]=b;
  25. sum+=road[i].w;
  26. }
  27. }
  28. }

最短路径

Dijkstra算法

Floyd算法

拓扑排序

关键路径

查找

顺序查找

  1. typedef struct{//查找表的数据结构
  2. ElemType *elem;//元素存储空间基址,建表时按实际长度分配,0号单元留空
  3. int TableLen;//表的长度
  4. }SSTable;
  5. //一般线性表的顺序查找
  6. int Search_Seq(SSTable ST,ElemType key){
  7. ST.elem[0]=key;//哨兵
  8. for(i=ST.TableLen;ST.elem[i]!=key;--i);//从后往前找
  9. return i;//若表中不存在关键字为key的元素,将查找到i为0时退出循环
  10. }

ASL(成功)=(n+1)/2
ASL(失败)=n+1
有序表的顺序查找:ASL(成功)=(n+1)/2;ASL(失败)=n/2+n/(n+1);

折半查找

  1. //仅适用于有序的顺序表
  2. int Binary_Search(SeqList L,ElemType key){
  3. int low=0,high=L.TableLen-1,mid;
  4. while(low<=high){
  5. mid=(low+high)/2;
  6. if(L.elem[mid]==key)
  7. return mid;
  8. else if(L.elem[mid]>key)
  9. high=mid-1;
  10. else
  11. mid=low+1;
  12. }
  13. return -1;//查找失败返回-1
  14. }

分块查找

  1. typedef struct{//索引表
  2. ElemType maxvalue;
  3. int low,high;//记录某块中第一个和最后一个元素的位置
  4. }indexElem;
  5. indexElem index[maxsize];//定义索引表

将长度为n的查找表均匀地分为b块,每块有s个记录,在等概率情况下,若在块内和索引表中均采用顺序查找,
则平均查找长度为: ASL=L1+Ls=(b+1)/2+(s+1)/2=(s^2+2s+n)/2s;
平均查找长度最小值为n^(1/2)+1;若对索引表采用折半查找,则平均查找长度为image.png

散列表

散列表(Hash Table),⼜称哈希表。是⼀种数据结构,特点是:数据元素的关键字与其存储地址直接相关
通过散列函数建立关键字和存储地址的关系 Addr=H(Key)。

常见的散列函数

设计目标:让不同关键字的冲突尽可能少
1.除留余数法
除留余数法 —— H(key) = key % p
2.直接定址法
H(key)=key或者H(key)=a*key+b
3.数字分析法
数字分析法 —— 选取数码分布较为均匀的若⼲位作为散列地址
4.平方取中法
平⽅取中法——取关键字的平⽅值的中间⼏位作为散列地址。

处理冲突的方法

1.拉链法
⽤拉链法(⼜称链接法、链地址法)处理“冲突”:把所有“同义词”存储在⼀个链表中
eg:假设散列表长为m,散列函数为H(K),用链地址处理冲突。试编写输入一组关键字构造散列表的算法(2019年)

  1. typedef struct HNode{
  2. int data;
  3. struct HNode *next;
  4. }HNode,*Hlist;
  5. void CreateHlist(Hlist L[],int m){
  6. int i,x;
  7. HNode *s;
  8. for(i=0;i<m;i++)
  9. L[i]=NULL;
  10. scanf("%d",&x);
  11. while(x!='#'){
  12. h=H(x);
  13. s=(HNode*)malloc(sizeof(HNode);
  14. s->data.key=x;
  15. s->next=L[h];
  16. L[h]=x;
  17. scanf("%d",&x);
  18. }
  19. }

其余处理冲突的方法见:https://www.yuque.com/docs/share/462a297a-7443-4e9b-9ad9-c32b07763dee?# 《散列查找》

排序

内部排序

内部排序指的是排序期间元素全部存放在内存中的排序

评价指标:1.稳定性:关键字相同的元素经过排序后相对顺序是否会发生改变
2.时间复杂度和空间复杂度

插入排序

算法思想:每次将⼀个待排序的记录按其关键字大小插入到前⾯已排好序的子序列中,直到全部记录插入完成。

直接插入排序

顺序查找找到应插入的位置,仅适用于顺序表,链表

  1. //用哨兵
  2. void InsertSort(ElemType A[],int n){
  3. int i,j;
  4. for(i=2;i<=n;i++){//依次将A[2]~A[n]插入到前面已排序序列
  5. if(A[i]<A[i-1]){//关键字小于前驱
  6. A[0]=A[i];//复制为哨兵
  7. for(j=i-1;A[0]<A[j];--j)//从后往前查找待插位置
  8. A[j+1]=A[j];
  9. A[j+1]=A[0];//复制到插入位置
  10. }
  11. }
  12. //不用哨兵
  13. void InsertSort(ElemtType A[],int n){
  14. int i,j,temp;
  15. for(i=1;i<n;i++){
  16. if(A[i]<A[i-1]){
  17. temp=A[i];
  18. for(j=i-1;j>=0&&A[j]>temp;--j)//检查前面已排好的序列
  19. A[j+1]=A[j];
  20. A[j+1]=temp;
  21. }
  22. }

算法效率分析
空间复杂度:O(1)
最好时间复杂度(全部有序):O(n)
最坏时间复杂度(全部逆序):O(n^2)
平均时间复杂度:O(n^2)
算法稳定性:稳定

折半插入排序

思路:先⽤折半查找找到应该插⼊的位置,再移动元素

当 low>high 时折半查找停⽌,应将 [low, i-1] 内的元素全部右移,并将 A[0] 复制到 low 所指位置;
当 A[mid]==A[0] 时,为了保证算法的“稳定性”,应继续在 mid 所指位置右边寻找插⼊位置;

  1. //折半插入排序
  2. void InsertSort(int A[],int n){
  3. int i,j,low,high,mid;
  4. for(i=2;i<=n;i++){
  5. A[0]=A[i];
  6. low=1;
  7. high=i-1;
  8. while(low<=high){ //折半查找 默认递增有序
  9. mid=(low+high)/2;
  10. if(A[mid]>A[0])
  11. high=mid-1;//查找左半子表
  12. else low=mid+1; //查找右半子表
  13. }
  14. for(j=i-1;j>=high+1;--j)
  15. A[j+1]=A[j];//统一后移元素
  16. A[high+1]=A[0];
  17. }
  18. }

希尔排序

希尔排序:先将待排序表分割成若⼲形如 L[i, i + d, i + 2d,…, i + kd] 的“特殊”⼦表,对各个⼦表分别进⾏直接插⼊排序。缩⼩增量d,重复上述过程,直到d=1为⽌。
先追求表中元素部分有序,再逐渐逼近全局有序

  1. //希尔排序
  2. void ShellSort(int A[],int n){
  3. int d,i,j;
  4. //A[0]只是暂存单元,不是哨兵,当j<=0,插入位置已到
  5. for(d=n/2;d>=1;d=d/2;)//步长变化
  6. for(i=d+1;i<=n;++i)
  7. if(A[i]<A[i-d]){//需要将A[i]插入有序增量子表
  8. A[0]=A[i]; //暂存A[0]
  9. for(j=i-d;j>0&&A[0]<A[j];j-=d)
  10. A[j+d]=A[j];//记录后移
  11. A[j+d]=A[0]; //插入
  12. }
  13. }

稳定性:不稳定!
适⽤性:仅适⽤于顺序表,不适⽤于链表

交换排序

冒泡排序

从后往前(或从前往后)两两⽐较相邻元素的值,若为逆序(即A[i-1]>A[i]),则交换它们,直到序列⽐较完。称这样过程为“⼀趟”冒泡排序。
若某⼀趟排序没有发⽣“交换”,说明此时已经整体有序。

  1. //冒泡排序
  2. void swap(int &a,int &b){ //交换
  3. int temp=a;
  4. a=b;
  5. b=temp;
  6. }
  7. void BubbleSort(int A[],int n){
  8. for(int i=0;i<n-1;i++){
  9. bool flag=false; //表示本趟冒泡是否发生交换的标志
  10. for(int j=n-1;j>i;j--)
  11. if(A[j-1]>A[j]){//若为逆序
  12. swap(A[j-1],A[j]);
  13. flag=true;
  14. }
  15. if(flag==false)
  16. return;//本趟遍历后没有发生交换,说明表已经有序
  17. }
  18. }

稳定性:稳定
适用性:顺序表,链表都可以
最好时间复杂度(全部有序):O(n)
最坏时间复杂度(全部逆序):O(n^2)
平均时间复杂度:O(n^2)

快速排序

算法思想:在待排序表L[1…n]中任取⼀个元素pivot作为枢轴(或基准,通常取⾸元素),通过⼀趟排序将待排序表划分为独⽴的两部分L[1…k-1]和L[k+1…n],使得L[1…k-1]中的所有元素⼩于pivot,L[k+1…n]中的所有元素⼤于等于pivot,则pivot放在了其最终位置L(k)上,这个过程称为⼀次“划分”。然后分别递归地对两个⼦表重复上述过程,直⾄每部分内只有⼀个元素或空为⽌,即所有元素放在了其最终位置上。

  1. //用第一个元素将待排排序序列分成左右两部分
  2. int Partition(int A[],int low,int high){
  3. int pivot=A[low]; //用第一个元素作为枢轴
  4. while(low<high){ //用low、high搜索枢轴的最终位置
  5. while(low<high&&A[high]>=pivot)
  6. --high;
  7. A[low]=A[high];//比枢轴小的元素移动到左端
  8. while(low<high&&A[low]<=pivot)
  9. ++low;
  10. A[high]=A[low]; //比枢轴大的元素移动到右端
  11. }
  12. A[low]=pivot; //枢轴元素存放到最终位置
  13. return low; //返回存放枢轴的最终位置
  14. }
  15. //快速排序
  16. void QuickSOort(int A[],int low,int high){
  17. if(low<high){//递归跳出的条件
  18. int pivotpos=Partition(A,low,high); //划分
  19. QuickSort(A,low,pivotpos-1); //划分左子表
  20. QuickSort(A,pivotpos+1,high); //划分右子表
  21. }
  22. }

空间复杂度=O(递归层数)
时间复杂度=O(n*递归层数)
最好时间复杂度=O(nlog2n)
最坏时间复杂度=O(n^2)
平均时间复杂度=O(nlog2n)

最好空间复杂度=O(log2n)
最坏空间复杂度=O(n)

快速排序是所有内部排序算法中平均性能最优的排序算法;
稳定性:不稳定

选择排序

选择排序:每⼀趟在待排序元素中选取关键字最⼩(或最⼤)的元素加⼊有序⼦序列

简单选择排序

算法思想:每⼀趟在待排序元素中选取关键字最⼩的元素加⼊有序⼦序列,n个元素的简单选择排序需要 n-1 趟处理;

  1. //简单选择排序
  2. void SelectSort(int A[],int n){
  3. for(int i=0;i<n-1;i++){ //一共进行n-1趟
  4. int min=i; //记录最小位置元素
  5. for(int j=i+1;j<n;j++){ //在A[i...n-1]中选择最小的元素
  6. if(A[j]<A[min])
  7. min=j; //更新最小元素位置
  8. if(min!=i)
  9. swap(A[i],A[min]);
  10. }
  11. }
  12. //交换函数
  13. void swap(int &a,int &b){
  14. int temp=a;
  15. a=b;
  16. b=temp;
  17. }

空间复杂度:O(1)
时间复杂度:O(n^2)
稳定性:不稳定
适⽤性:既可以⽤于顺序表,也可⽤于链表

堆排序

n个关键字序列L[1…n] 满⾜下⾯某⼀条性质,则称为堆(Heap):
① 若满⾜:L(i)≥L(2i)且L(i)≥L(2i+1) (1 ≤ i n/2 )—— ⼤根堆(⼤顶堆)
② 若满⾜:L(i)≤L(2i)且L(i)≤L(2i+1) (1 ≤ i n/2 )—— ⼩根堆(⼩顶堆)
思路:把所有⾮终端结点都检查⼀遍,是否满⾜⼤根堆的要求,如果不满⾜,则进⾏调整,检查当前结点是否满⾜ 根≥左、右;若不满⾜,将当前结点与更⼤的⼀个孩⼦互换;若元素互换破坏了下⼀级的堆,则采⽤相同的⽅法继续往下调整(⼩元素不断“下坠”)

  1. //建立大根堆
  2. void BuildMaxHeap(int A[],int len){
  3. for(int i=len/2;i>0;i--) //从后往前调整所有非终端结点
  4. HeadAdjust(A,i,len);
  5. }
  6. //将以k为根的子树调整为大根堆
  7. void HeadAdjust(int A[],int k,int len){
  8. A[0]=A[k]; //A[0]暂存子树的根节点
  9. for(int i=2*k;i<=len;i*=2){ //沿key较大的子结点向下筛选
  10. if(i<len&&A[i]<A[i+1])
  11. i++; //取key较大的子结点的下标
  12. if(A[0]>=A[i])
  13. break; //筛选结束
  14. else{
  15. A[k]=A[i]; //将A[i]调整到双亲结点上
  16. k=i; //修改k值,一遍继续向下筛选
  17. }
  18. }
  19. A[k]=A[0]; //被筛选结点的值放入最终位置
  20. }
  21. //堆排序的完整逻辑
  22. void HeapSort(int A[],int len){
  23. BuildMaxHeap(A,len); //初始建堆
  24. for(int i=len;i>1;i--){ //n-1趟的交换和建堆过程
  25. swap(A[i],A[1]); //堆顶元素和堆底交换
  26. HeadAdjust(A,1,i-1); //将剩余的待排序元素整理成堆
  27. }
  28. }

对于⼩根堆:
新元素放到表尾,与⽗节点对⽐,若新元素⽐⽗节点更⼩,则将⼆者互换。新元素就这样⼀路“上升”,直到⽆法继续上升为⽌;被删除的元素⽤堆底元素替代,然后让该元素不断“下坠”,直到⽆法下坠为⽌;

归并排序

m路归并,每选出⼀个元素需要对⽐关键字 m-1 次
核⼼操作:把数组内的两个有序序列归并为⼀个

  1. int *B=(int *)malloc((n+1)*sizeof(int)); //辅助数组B
  2. //A[low...mid]和A[mid+1...high]各自有序,将两个部分归并
  3. void Merge(int A[],int low,int mid,int high){
  4. int i,j,k;
  5. for(k=low;k<=high;k++)
  6. B[k]=A[k];
  7. for(i=low,j=mid+1,k=i;i<=mid&&j<=high;k++){ // 归并
  8. if(B[i]<=B[j]) //两个元素相等时,优先使⽤靠前的那个(稳定性)
  9. A[k]=B[i++]; //将较小值复制到A中
  10. else
  11. A[k]=B[i++];
  12. }
  13. while(i<=mid)
  14. A[k++]=B[i++]; //没有归并完的部分复制到尾部
  15. while(j<=high)
  16. A[k++]=B[j++];
  17. }
  18. //归并排序
  19. void MergeSort(int A[],int low,int high){
  20. if(low<high){
  21. int mid=(low+high)/2; //从中间划分
  22. MergeSort(A,low,mid); //左半部分归并排序
  23. MergeSort(A,mid+1,high);//右半部分归并排序
  24. MergeSort(A,low,mid,high);//归并
  25. }
  26. }

基数排序

https://www.yuque.com/docs/share/a00edd79-fbd3-448b-8d7c-2a1758bb2f5e?# 《基数排序》

外部排序

https://www.yuque.com/docs/share/c37bcda9-cd8d-4add-99da-b38ab458fd3b?# 《外部排序》
https://www.yuque.com/docs/share/ef1b16bc-df98-481c-80d7-949e6835db4c?# 《败者树》
https://www.yuque.com/docs/share/5fde9340-7153-4b64-b848-e224d20fb8ae?# 《置换—选择排序》
https://www.yuque.com/docs/share/964da5c6-0966-443d-8cf9-5a42599cdb2e?# 《最佳归并树》