数据结构

数据结构复习

 biubiu     2020-01-13   20619 words    & views

数据结构复习

第一章:引言

1.基本概念

数据:一切能输入到计算机中并能被计算机程序识别和处理符号的

数据类型:value+operations

数据元素(data element):数据的基本单位

data item:构成数据元素的最小单位

data object:具有相同形式的数据元素的集合

ADT :Abstract data Type 抽象数据类型

算法的五个重要特性:确定性,可行性,输入,输出,有限性

分析算法的四个方面:正确性,可读性,健壮性,效率(时间,空间)

第二章:线性结构

1.线性表

线性存储结构:

元素之间逻辑的相继关系用物理上的相邻关系来表示(逻辑上相邻的元素在物理位置上也相邻)

数组线性表数据插入

int insertelemm( list *L,int x,int i)
{
	int j;
	for(j=L.last;j>=i;j--)
	{
		L.data[j+1]=L.data[j];		
	}
	L.data[i]=x;
	L.last++;
}

优点:结构简单,存储效率高

缺点:插入,删除算法效率低,对长度大的小型吧,操作不方便

2.链表 Linkedlist

元素之间的逻辑相邻关系用指针来表示

插入方式

void insert(ListNode *head,int pos,int x)//在pos后插入
{
	ListNode *p,*q;
	p=head;
	int i;
	for(i=0;i<posi++)
	{
		p=p->next;
	}
	q=new ListNode(x);
	q->next=p->next;
	p->next=q;
}

优点:插入删除比较快,灵活

缺点:访问元素费时间

//链表的就地旋转
void convert(Linklist *la)
{
    Linklist *p,*q;
    p=la->next;
    la->next=NULL;
    while(p!=NULL)
    {
        p=q;
        p=p->next;
        q->next=la;
        la=q;
    }
}

3.栈结构 Stack

仅限在表尾进行插入和删除的线性表(后进先出)

Operation:

入栈:push 出栈 pop 得到头结点 top

递归实现全排列

#include <iostream>
using namespace std;
void swap(int &a,int &b){
    int temp=a;
    a=b;
    b=temp;
}
void perm(int list[],int low,int high){
    if(low==high){   //当low==high时,此时list就是其中一个排列,输出list
        for(int i=0;i<=low;i++)
            cout<<list[i];
        cout<<endl;
    }else{
        for(int i=low;i<=high;i++){//每个元素与第一个元素交换
            swap(list[i],list[low]); //将递归的第一个元素与后面的每个元素交换
            perm(list,low+1,high); //交换后,得到子序列,用函数perm得到子序列的全排列 交换后继续执行实现后面的数的交换
            swap(list[i],list[low]);//最后,将元素交换回来,复原,然后交换另一个元素  执行输出后,按从内到外交换回来,返回出来原函数。
        }
    }
}
int main()
{
 
int list[]={1,2,3};
perm(list,0,2);
return 0;
}

4.队列 Queue

只允许在一段进行插入操作,在另外一段进行删除操作的线性表(先进先出)

Operations:

initQueue()//初始化:
empty()//判断是否为空
pop()//出队
push()//入队

5.字符串 String

字符串匹配算法

next 求法人为看第j项前 最长相等前后缀

//nextval求法
for(i=1;i<max;i++)
{
	if(T[j]==T[next[j]])
	{
		nextval[j]=nextval[next[j]];
	}
	else nextval[j]=next[j];
}

6.数组和广义表

特殊矩阵的压缩

1)对称矩阵

2)稀疏矩阵

三元法

三元法的转置

粗暴算法:

改进算法:

使用两个数组num,cpot

num记录m中每列元素个数,cpot通过num求出每一列元素开始位置

然后扫描放进去

广义表定义

第三章:树

1.树的定义

​ 结点的度:结点的子树

​ 分支结点:度不为0的结点

​ 叶:度为0的结点

​ 子女结点:某结点子树的根节点

​ 双亲结点:某个结点是其子树之根的双亲

​ 兄弟结点:具有同一双亲的所有结点

2.存储设计

1)双亲表示法

​ 每个结点有一个编号和存储信息,并存储他们的双亲的编号

2)孩子表示法

​ 孩子表示法主要描述的是结点与孩子的关 系。由于每个结点的孩子个数不定,所以利 用链式存储结构更加适宜。

3)孩子兄弟表示法

​ 孩子兄弟表示法也是一种链式存储结构。它通过描述每 个结点的一个孩子和兄弟信息来反映结点之间的层次关系, 其结点结构为:

其中,firstchild为指向该结点第一个孩子的指针, nextsibling为指向该结点的下一个兄弟,item是数据元素 内容。

3.二叉树

​ 1) 若二叉树的层次从1开始, 则在二叉树 的第 i 层最多有 2^i-1^个结点。(i >=1)

​ 2)高度为k的二叉树最多有 2 ^k^ -1个结点。

​ 3)对任何一棵二叉树, 如果其叶结点个 数为n0 , 度为2的非叶结点个数为 n~2~, 则有 n~0~=n~2~+1

​ 4)具有n个结点的完全二叉树的 高度为 (log~2~n)~up~ +1

​ 5)image-20191226170559935

4.树与二叉树的相互转化

1)树转化为二叉树

将树转换成二叉树的步骤是:

1.加线。就是在所有兄弟结点之间加一条连线;

2.抹线。就是对树中的每个结点,只保留他与第一个孩子结点之间的连线,删除它与其它孩子结点之间的连线;

3.旋转。就是以树的根结点为轴心,将整棵树顺时针旋转一定角度,使之结构层次分明。

image-20191230193040050

2)二叉树转化为树

1.若某结点的左孩子结点存在,将左孩子结点的右孩子结点、右孩子结点的右孩子结点……都作为该结点的孩子结点,将该结点与这些右孩子结点用线连接起来;

2.删除原二叉树中所有结点与其右孩子结点的连线;

3.整理(1)和(2)两步得到的树,使之结构层次分明。

image-20191230193151156

5.二叉树的遍历

1)中序遍历

左子树->根节点->右子树

2)前序遍历

根节点->左子树->右子树

3)后序遍历

左子树->右子树->根节点

4)层序遍历

一层一层的遍历

void cengxu(Node *root){
	queue<Node*> q;
	Node *front;
	if(root == NULL) return;
	q.push(root);
	while(!q.empty()){
		front = q.front();
		q.pop();
		if(front->left) q.push(front->left);
		if(front->right) q.push(front->right);
		cout << front->key<< " ";
	}
}

6.哈夫曼编码

#include<iostream>
#include<vector>
using namespace std;
struct Huff_Node
{
	int weight;
	char str;
	string code;
	int left;
	int right;
	int parent;
};
int main()
{
    int n;
    cin>>n;
    int a;
    char b;
    vector<int> num;
    vector<char> ss;
    int i,j,k;
    for(i=0;i<n;i++)
    {
        cin>>a;
        cin>>b;
        num.push_back(a);
        ss.push_back(b);       
    }
    vector<int> weight;
    for(i=0;i<n-1;i++)
    {
        for(j=i+1;j<n;j++)
        {
            if(num[j]>num[i])
            {
                b=ss[i];
                ss[i]=ss[j];
                ss[j]=b;
                a=num[i];
                num[i]=num[j];
                num[j]=a;
            }
        }
    }
    for(i=0;i<n;i++)
    {
        weight.push_back(num[i]);
    }
    Huff_Node *huff = new Huff_Node[n * 2 - 1];
    for(i=0;i<2*n-1;i++)
    {
        huff[i].left=-1;
        huff[i].right=-1;
        huff[i].parent=-1;
        huff[i].code="";
        if(i<n)
        {
            huff[i].str=ss[i];
            huff[i].weight=num[i];
        }
    }
        int min1,min2,left,right;
        for(j=n;j<2*n-1;j++)
        {
            min1=1000;
            min2=1000;
            left=-1;
            right=-1;
            for(k=0;k<j;k++)
            {
                if(huff[k].parent==-1)
                {
                    if(huff[k].weight < min1)
                    {
                        min2 = min1;
					    min1 = huff[k].weight;
					    right = left;
					    left = k;
                    }
                    else if(huff[k].weight < min2)
                    {
                        min2=huff[k].weight;
                        right=k;
                    }
                }
            }
            huff[j].weight = huff[left].weight + huff[right].weight;
		    huff[j].left = left;
		    huff[j].right = right;
		    huff[left].parent = j;
		    huff[right].parent = j;
        }
    for (int i = 0; i < n; i++)
	{
		string code = "";
		int j = i;
		while (huff[j].parent != -1)
		{
			int k = huff[j].parent;
			if (huff[k].left==j)
			{
				code = "0" + code;
			}
			else
			{
				code = "1" + code;
			}
			j = k;
		}
		huff[i].code = code;
	}
    for(i=0;i<n;i++)
    {
        cout<<huff[i].weight<<huff[i].code<<endl;
    }
}

第四章 图

1.基本概念

​ 1)一个顶点v的度是与它相关联的边的条数;

​ 2)顶点的出度: 以顶点v为弧尾的弧的数目;

​ 3)顶点的入度: 以顶点v为弧头的弧的数目。

​ 4)顶点的连通性:在无向图中, 若从顶点vi到顶点vj (i≠j)有路径, 则称顶点vi与vj是连通的。

​ 5)连通图:如果一个无向图中任意一对顶点都是连通的, 则称 此图是连通图。

​ 6) 连通分量:非连通图的极大连通子图叫做连通分量。

​ 7)强连通性:在有向图中, 若对于每一对顶点vi和vj ( i≠j), 都存在一条从vi到vj和从vj到vi的有向路径,则称顶点vi与 vj是强连通的。

​ 8)强连通图:如果一个有向图中任意一对顶点都是强连通的, 则 称此有向图是强连通图。

2.图的表示

1)邻接矩阵
typedef struct{
    vertextype v[n];
    int juzhen[n][n];
    int vexnum,arcnum;
}MGraph;

image-20191226203331490

2)邻接表
class EdgeNode {
 public:
	int adjvex; //边指向哪个顶点的索引
	int weight;
	EdgeNode* next;
};
// 顶点表结构
class VertexNode {
 public:
	int value; //顶点的值,为了简化与序号相同,第一个是0
	EdgeNode *firstedge;
};
// 图结构
class GraphList {
 public:
	VertexNode adjList[MAXVEX];
	int numVertex;
	int numEdges;
};
void createGraph(GraphList* g)
{
	int k,m;
	EdgeNode *t;
	cin>>k;
	cin>>m;
	int p,q,num;
	int i;
	g->numEdges=m;
	g->numVertex=k;
	for(i=0;i<k;i++)
	{
		cin>>g->adjList[i].value;
	}
	for(i=0;i<m;i++)
	{
		cin>>p>>q>>num;
		if(g->adjList[p].firstedge==NULL)
		{
			g->adjList[p].firstedge=new EdgeNode;
			g->adjList[p].firstedge->adjvex=q;
			g->adjList[p].firstedge->weight=num;
		}
		else
		{
			t=g->adjList[p].firstedge;
			while(t->next!=NULL)
			{
				t=t->next;
			}
			t->next=new EdgeNode;
			t->next->adjvex=q;
			t->next->weight=num; 
		}
	}
}

3.图的遍历

1)DFS深度优先搜索

​ (1)从图中的某个顶点v出发,访问之;

​ (2)依次从顶点v的未被访问过的邻接点出 发,深度优先遍历图,直到图中所有和顶 点v有路径相通的顶点都被访问到;

​ (3)若此时图中尚有顶点未被访问到,则另选 一个未被访问过的顶点作起始点,重复上述(1)(2)的操作,直到图中所有的顶 点都被访问到为止。

void DFS(GraphList *g,int v,int *visit)
{
	cout<<g->adjList[v].value<<" ";
	visit[v]=1;
	EdgeNode *e;
	e=g->adjList[v].firstedge;
	while(e!=NULL)
	{
		if(visit[e->adjvex]==0)
		DFS(g,e->adjvex,visit);
		e=e->next;
	}
	
}
void DFSTraverse(GraphList *g)
{
	int *visit=new int [g->numVertex];
	int i;
	for(i=0;i<g->numVertex;i++)
	{
		visit[i]=0;
	}
	for(i=0;i<g->numVertex;i++)
	{
		if(visit[i]==0)
		{
			DFS(g,i,visit);
		}
	}
}
2)BFS广度优先搜索

​ (1)从图中的某个顶点v出发,访问之;

​ (2)依次访问顶点v的各个未被访问过的邻接点,将v的全部邻接点都访问到;

​ (3)分别从这些邻接点出发,依次访问它们的 未被访问过的邻接点,并使“先被访问的顶点的邻接点”先于“后被访问的顶点的邻接 点”被访问,直到图中所有已被访问过的顶 点的邻接点都被访问到。

void BFS(GraphList *g,int v,int *visit)
{
	cout<<g->adjList[v].value<<" ";
	visit[v]=1;
	queue<int> q;
	q.push(v);
	while(!q.empty())
	{
		int w=q.front();
		q.pop();
		EdgeNode *e;
		e=g->adjList[w].firstedge;
		while(e!=NULL)
		{
			if(visit[e->adjvex]==0)
			{
				cout<<e->adjvex<<" ";
				visit[e->adjvex]=1;
				q.push(e->adjvex);
			}
			e=e->next;		
		}		
	 } 
}
void BFSTraverse(GraphList *g)
{
	int *visit=new int [g->numVertex];
	int i;
	for(i=0;i<g->numVertex;i++)
	{
		visit[i]=0;
	}
	for(i=0;i<g->numVertex;i++)
	{
		if(visit[i]==0)
		{
			BFS(g,i,visit);
		}
	}
}

4.最小生成树

1)Prim算法

​ (1)在连通网的顶点集合V中,任选一个顶点, 构成最小生成树的初始顶点集合U;

​ (2)在U和V-U中各选一个顶点,使得该边的权值最小,把该边加入到最小生成树的边集TE中, 同时将V-U中的该顶点并入到U中;

​ (3)反复执行第(2)步,直至V-U=Ø为止。

引入一个辅助数组记录现有顶点到各顶点的最小权值

顶点 0 1 2 3 4 5…
lowcost            
adjvex            
void Prim(Graph G,MST & T,int u)
{
    float *lowcost=new float[vernum];
    int *adjvex=new int [vernum];
    for(int i=0;i<vernum;i++)
    {
        lowcost[i]=G.edge[u][i];
        adjvex[i]=u;
    }
    adjvex[u]=-1;
    int k=0;
    for(i=0;i<G.n;i++)
    {
        if(i!=u)
        {
            min=10000;
            int v=0;
            for(j=0;j<vernum;j++)
            {
                if(adjvex[j]!=-1&&lowcost[j]<min)
                {
                    v=j;
                    min=lowcost[j];
                }
            }
        }
        if(v)
        {
            T[k].tail=adjvex[v];
            T[k].head=v;
            T[k++].cost=lowcost[v];
            adjvex[j]=-1;
            for(j=0;j<G.n;j++)
            {
                if(adjvex!=-1&&G.edge[v][j]<lowcost[j])
                {
                    lowcost[j]=G.edge[v][j];
                    adjvex[j]=v;
                }
            }
        }
    }
}
2)Kruskal算法

基本思想:

设有一个有 n 个顶点的连通网络 N = { V, E },最初先 构造一个只有 n 个顶点,没有边的非连通图 T = { V,  }, 图中每个顶点自成一个连通分量。当在 E 中选到 一条具有最小权值的边时,若该边的两个顶点落在不同 的连通分量上,则将此边加入到 T 中;否则将此边舍 去,重新选择一条权值最小的边。如此重复下去,直 到所有顶点在同一个连通分量上为止。

5. AOV网

用顶点表示活动,用有向边表示活动间 的优先关系。v~i~ 必须先于活动v~j~进行。这种有 向图叫做顶点表示活动的AOV网络(Activity On Vertices)。

1)拓扑排序

① 输入AOV网络。令 n 为顶点个数。

② 在AOV网络中选一个没有直接前驱的顶点, 并输出之;

③ 从图中删去该顶点, 同时删去所有它发出 的有向边;

④ 重复以上 ②、③步, 直到

 全部顶点均已输出,拓扑有序序列形成, 拓扑排序完成;

 图中还有未输出的顶点, 但已跳出处理 循环。说明图中还剩下一些顶点, 它们都有直接前驱。这时网络中必存在有向环。

void Graphcircle(GraphList *g){
	int count[g->numVertex],del[g->numVertex];
		for(int i=0;i<g->numVertex;i++){
			del[i]=0;
            count[i]=0;
		}
	for(int i=0;i<g->numVertex;i++){
		EdgeNode *p=g->adjList[i].firstedge;
		while(p){
			count[p->adjvex]++;
			p=p->next;
		}
	}//初始化count
	stack<int> sta;
	int num=0;
	for(int i=0;i<g->numVertex;i++){
		if(count[i]==0){
			sta.push(i);
		}
	}//第一个入栈 
	while(!sta.empty()){
		int w=sta.top();
		sta.pop();
        //cout<<w<<endl;
		num++;
		del[w]=1;//记录该点是否被删除
		EdgeNode *p=g->adjList[w].firstedge;
		while(p){
			count[p->adjvex]--;
            if(count[p->adjvex]==0){
                sta.push(p->adjvex);
              
            }
			p=p->next;
		}
		for(int i=0;i<g->numVertex;i++){
			if(count[i]==0 && del[i]==0)
            {
				sta.push(i);
			}
		}
	}
}
5)AOE网络

从源点到各个顶点,以至从源点到汇点的有向路径可 能不止一条。这些路径的长度也可能不同。完成不同 路径的活动所需的时间虽然不同,但只有各条路径上 所有活动都完成了,整个工程才算完成。

因此,完成整个工程所需的时间取决于从源点到汇点 的最长路径长度,即在这条路径上所有活动的持续时 间之和。这条路径长度最长的路径就叫做关键路径 (Critical Path)。

image-20191226215400843

image-20191226215412592

image-20191226215547202

image-20191226215558606

image-20191226215610867

int *etv,*ltv;
int *stack2;
int top2;
Status TopologicalSort(GraphAdjList GL)
{
    EdgeNode *e;
    int i,k,gettop;
    int top=0;
    int count=0;
    int *stack;
    stack=new int(GL -> vernum);
    for(i=0;i<GL->vernum;i++)
    {
       	if(GL->adjList[i].in==0)
        {
            stack[++top]=i;
        }
    }
    evt=new int(GL -> vernum);
    for(i=0;i<GL->vernum;i++)
    {
        evt[i]=0;
    }
    stack=new int(GL->vernum);
    while(top!=0)
    {
        gettop=stack(top--);
        count++;
        stack2[++top2]=gettop;
        for(e=GL->adjList[gettop].firstedge;e;e=e->next)
        {
            k=e->adjvex;
            GL->adjList[k].in--;
            if(GL->adjList[k]==0)
            {
                stack[++top]=k;
            }
            if(etv[gettop]+e->weight>etv[k])
            {
                etv[k]=etv[gettop]+e->weight;
            }
        }
    }
}
void CriticalPath(GraphAdjList GL)
{while
    EdgeNode *e;
    int i,gettop,k,j;
    int ete,lte;
    TopologicalSort(GL);
    ltv=new int (GL->vernum);
    for(i=0;i<GL->vernum;i++)
    {
        ltv[i]=etv[GL->vernum-1];
    }
    while(top2!=0)
    {
        gettop=stack2[top2--];
        for(e=GL->adjList[gettop];e;e=e->next)
        {
            k=e->adjvex;
            if(ltv[k]-e->weight<ltv[gettop])
            {
                ltv[gettop]=ltv[k]-e->weight;
            }
        }
    }
 	for(j=0;j<GL->vernum;j++)
 	{
     	for(e=GL->adjList[j].firstedge;e;e=e->next)
        {
            k=e->adjvex;
            ete=etv[j];
            lte=ltv[k]-e->weight;
            if(ete==lte)
            {
                cout<<GL->adjList[j].data<<GL->adjList[k].data<<e->weight;
            }
        }
 	}
}

6.最短路径

1)Dijkstra算法

​ ① 初始化: S ← { v0 }; dist[j] ← Edge[0] [j], j = 1, 2, …, n-1; // n为图中顶点个数

​ ② 求出最短路径的长度: dist[k] ← min { dist[i] }, i  V- S ; S ← S U { k };

​ ③ 修改: dist[i] ← min{ dist[i], dist[k] + Edge [k] [i] }, 对于每一个 i  V- S ;

​ ④ 判断:若 S = V, 则算法结束,否则转 ②。

void ShortestPath(MTGraph G,int v)
{
    EdgeData dist[G.num];
    int path[G.num];//记录该点的上一个顶点
    int S[G.num];//记录是否已经放进路径当中
    for(int i=0;i<G.num;i++)
    {
        dist[i]=G.Edge[v][i];
        S[i]=0;
        if(i!=v&&dist[i]<max)
        {
            path[i]=v;
        }
        else path[i]=-1;
    }
    S[v]=1;dist[v]=0;
    for(int i=0;i<G.num-1;i++)
    {
        int min=10000;
        for(int j=0;j<G.num;j++)
        {
            if(!S[j]&&dist[j]<min)
            {
                min=dist[j];
                u=j;
            }
        }
        S[u]=1;
        for(int k=0;k<G.num;k++)
        {
            if(!S[k]&&G.edge[u][k]<max&&G.edge[u][k]+dist[u]<dist[k])
            {
                dist[k]=G.edge[u][k]+dist[u];
                path[k]=u;
            }
        }
    }
    for(int i=0;i<G.num;i++)
    {
        //路径输出
    }
}
2)所有顶点之间的最短路径

image-20191227142052814

image-20191227142100855

image-20191227142108999

代码:

#include<iostream>
#include <vector>
using namespace std;
const int inf = 999999;
const int n = 6;
int L[n][n] = {   0,  7,  9,inf,inf, 14,
                  7,  0, 10, 15,inf,inf,
                  9, 10,  0, 11,inf,  2,
                inf, 15, 11,  0,  6,inf,
                inf,inf,inf,  6,  0,  9,
                14, inf,  2,inf,  9,  0
                 };               //存储图中的路径
int dis[n][n];        //存储源点到各个顶点的最短路径
 
vector<int> path[n];
int main()
{
	for (int i = 0; i < n; i++)              //初始化
	{
		
		for (int j = 0; j < n; j++)
		{
			dis[i][j] = L[i][j];
			path[i][j].push_back(i+1);
		    path[i][j].push_back(j+1);
		}
	}
	for (int k = 0; k < n; k++)
	{
		for (int i = 0; i < n; i++)
	   {
		for (int j = 0; j < n; j++)
		{
				//dis[i] = min(dis[i],dis[j] + L[j][i]);
			if (dis[k][i] > dis[k][j] + L[j][i])               //求源点到节点的最短路径,利用现有的L矩阵
			{
				dis[k][i] = dis[k][j] + L[j][i];
 
				path[k][i].clear();                         //保存并更新路径
				path[k][i].insert(path[k][i].end(), path[k][j].begin(),path[k][j].end());
				path[k][i].push_back(i+1);
			}
		}
	}
	}
	
	vector<int>::iterator ite;
	for (int k = 0; k < n; k++)
	{
		for (int i = 0; i < n; i++)
	{
		cout << "源点"<<k+1<<"到"<<i+1<<"的最短路径长度:" << dis[k][i]<<endl<<"Path:";
	    for (ite = path[k][i].begin(); ite !=path[k][i].end();++ite) {
		    if (ite == path[k][i].begin())
			    cout << *ite;
		    else
			    cout << "->"<< *ite ;
	}
	    cout << endl;
	}
	}
	return 0;
}

7.网络流

image-20191227144844551

image-20191227144857143

image-20191227144909764

如果一个节点被移去是,图被分成两个或多个部分,则称该节点为关节点

没有关节点的连通图为双连通图

第五章 查找

1.基本概念

​ 查找:就是在数据集合中寻找满足某种条 件的数据对象。

​ 查找表:是由同一类型的数据元素(或记录) 组成的数据集合。

​ 关键字:数据元素中某个数据项的值, 用以标识一个数据元素。

​ 主关键字:可唯一地标识一个数据元素的关键字。

​ 次关键字:用以识别若干记录的关键字。

2.查找方法

1)顺序查找

查找过程:从表中最后一个元素开始,顺序用各元 素的关键字与给定值x进行比较,若找到与其值相等 的元素,则查找成功,给出该元素在表中的位置; 否则,若直到第一个记录仍未找到关键字与x相等的 对象,则查找失败。

for(i=n;i>0;i--)
{
	if(a[i]==p)
	return i;
}

顺序查找 时间复杂性O(n)

插入、删除的时间复杂性O(1)

2)折半查找

前提条件:表中的关键字有序

⑴ 取中间位置Mid:Mid=(Low+High)/2 ;

⑵ 比较中间位置记录的关键字与给定的K值:

​ ① 相等: 查找成功;

​ ② 大于:待查记录在区间的前半段,修改上界指 针: High=Mid-1,转⑴ ;

③ 小于:待查记录在区间的后半段,修改下界指 针:Low=Mid+1,转⑴ ;

直到越界(Low>High),查找失败。

int Bin_search(STable ST,int key)
{
    int low=0,high=ST.lenth-1,mid;
    while(low<high)
    {
        mid=(low+high)/2;
        if(key==ST[mid])
        {
            return mid;
        }
        else if(key<ST[mid])
        {
            high=mid-1;
        }
        else
            low=mid+1;
    }
    return 0;
}

折半查找的时间负责性为O(log n)

插入、删除的时间复杂性为O(n)

3)分块查找

分块查找:又称索引顺序查找,是 前面两种查找方法的综合。

​ ① 将查找表分成几块。块间有序,即第i+1块的所有记录关 键字均大于(或小于)第i块记录关键字;块内无序。

② 在查找表的基础上附加一个索引表,索引表是按关键字有 序的。
int Block_search(int *ST,index *ind,int key,int n,int b)//n为表长,b为块数
{
    int i=0,j,k;
    while(i<b)
    {
        if(ind[i].maxkey>key)
            i++;
    }
    if(i>b){cout<<"not found";return 0;}
    
   	j=ind[i].starypos;
    while(j<n)
    {
        if(ST[j].key==key)
        {
            break;
        }
        j++;
    }
    if(j>n||ST[j].key!=key)
    {
        cout<<"no found";
        retuen 0;
    }
    return j;
}
4)优先队列

给定一个具有m个关键字的序列,如果在该序列 中,满足: 对 i= m/2→ 1的元素,k~i~<=k~2i~且k~i~<=k~2i+1~,则该序列称为优先队列(堆)

image-20191227160119132

image-20191227160136825

构建代码:

void HeapAdjust(HeapType &H ,int s,int m)
{//最大堆的向下调整算法
	int rc=H.r[s];
	for(int j=2*s;j<m;j*=2)
	{
	if((j<m)&&(H.r[j].key<H.r[j+1].key))
	{
		j++;
	}
	if(rc.key>H.r[j].key)break;
	H.rs[s].key=H.rs[k].key;s=j
	}
	H.r[s]=rc;
}

插入O(log n)

查找删除O(1)

5)哈希表

散列技术的基本思想:

​ 把元素的存储位置和该记录的关键字的值之间建立一个映射关系。关键字的值在这种映射关系下的像,就是相应记录在表中的存储位置。

相关概念:

​ 散列:将记录按照其关键字的散列地址存储到散列表中的过程

​ 散列冲突:不同关键字具有相同的散列地址

相关构造方法:

1.直接定址法(线性函数)

2.数字分析法

image-20191227200513959

3.平方取中法

image-20191227200552750

4.折叠法

image-20191227200755521

5.除留余数法

设散列表中允许的地址数为m,取一个不大于m,但最接近于或 等于m的质数p,或选取一个不小于20的质因数的合数作为除数, 利用以下公式把关键字转换成散列地址。散列函数为:

$hash ( key ) = key $%p ,$ p <= m$

image-20191227201128601

6.散列技术

​ 1)线性探测法

image-20191227201337449

image-20191227201427511

​ 2)二次探测法

image-20191227201451918

​ 3)再哈希法

image-20191227201641832

​ 3)链地址法

image-20191227201730241

image-20191227202013858

​ 4)哈希查找

image-20191227202137913

image-20191227202152419

第六章 排序

1.基本概念

​ 排序:将一组杂乱无章的数据排列成一个按关键字有序的序列

​ 数据表:它是特定排序数据对象的有限集合

​ 关键字:通常数据对象有多个属性域,即多个数据成员组成,其 中有一个属性域可用来区分对象,作为排序依据。该域即为关键字。每 个数据表用哪个属性域作为关键字,要视具体的应用需要而定。即使是 同一个表,在解决不同问题的场合也可能取不同的域做关键字。

2.排序方法

1)插入排序

​ 每步将一个待排序的对象,按其关键字大小,插入到前面已经排好序的一组对象的适当位置上,直到对象全部插入为止。

image-20191227203347300

void InsertSort(int *a)
{
    int i,j,temp=0;
    for(i=1;i<n;i++)
    {
        temp=a[i];
        j=i-1;
        for(;j>=0&&a[j]>temp;j--)
        {
            a[j+1]=a[j];
        }
        a[j+1]=temp;
    }
}

算法分析:

最好情况为本来有序O(n)

稳定为O(n^2^)

2)希尔排序

基本思想:

先将整个待排对象序列按照一定间隔分割成为若干子序列,分别进行直接插入排序,然后缩小间隔,对整个对象序列重复以上的划分子序列和分别排序工作,直到最后间隔为1,此时整个对象序列已 “基本有序”,进行最后一次直接插入排序。

void shellsort(int *a)
{
    int d=n;
    while(d!=1)
    {
        d/=2;
        for(int x=0;x<d;x++)
        {
            for(int i=x+d;i<n;i+=d)
            {
                int temp=a[i];
                int j;
                for(j=i-d;j>=0&&a[j]>temp;j-=d)
                {
                    a[j+d]=a[j];
                }
                a[j+d]=temp;
            }
        }
    }
}

Knuth利用大量的实验统计资料得 出,当n很大时,关键字平均比较 次数和对象平均移动次数大约在 n ^1.25^ 到 1.6*n^1.25^ 的范围内。这是在利 用直接插入排序作为子序列排序方 法的情况下得到的。

3)冒泡排序
void bubblesort(int *a)
{
    int i,j;
    bool change=1;
    for(i=n-1;i>0&&change==1;i--)
    {
        change=0;
        for(j=0;j<i;j++)
        {
            if(a[j+1]<a[j])
            {
                int temp=a[j+1];
                a[j+1]=a[j];
                a[j]=temp;
                change=1;
            }
        }
    }
}

算法特点:

至少比较一次,至多比较n-1次,可实现部分排序,稳定排序

最理想O(n) 最坏情况O(n^2^)

4)快速排序
int getmid(int *a,int low,int high)
{
    int temp=a[low];
    while(low<high)
    {
        while(low<high&&a[high]>=temp)
        {
            high--;
        }
        a[low]=a[high];
        while(low<high&&a[low]<=temp)
        {
            low++;
        }
        a[high]=a[low];
    }
    a[low]=temp;
    return low;
}
void quicksort(int *a,int low,int high)
{
    if(low<high)
    {
        int mid=getmid(a,low,high);
        quicksort(a,low,mid-1);
        quicksort(a,mid+1,high);
    }
}

分析:

快速排序是一种不稳定的排序方法

对于n比较大的时候,速度快,但对于n比较小的时候,比其他排序慢

5)选择排序
void selectsort(int *a)
{
    int i,j;
    for(i=0;i<n;i++)
    {
        for(j=i+1;j<n;j++)
        {
            if(a[i]>a[j])
            {
                int temp=a[i];
                a[i]=a[j];
                a[j]=temp;
            }
        }
    }
}

分析:

直接选择排序是一种不稳定的排序方法

时间复杂度为O(n^2^)

6)堆排序

参考上述优先队列

7)归并排序
void Merge(int *a,int l,int mid,int r)
{
    int *temp=new int[r-l+1];
    int i=0;
    int p1=l;
    int p2=mid+1;
    while(p1<=mid&&p2<=r)
    {
        temp[i++]=a[p1]<a[p2]?a[p1++]:a[p2++];
    }
    while(p1<=mid)
    {
        temp[i++]=a[p1++];
    }
    while(p2<=r)
    {
        temp[i++]=a[p2++];
    }
    for(i=0;i<r-l+1;i++)
    {
        a[l+i]=temp[i];
    }
}
void Msort(int *a,int l,int r)
{
    if(l==r)return;
    int mid=l+(r-l)/2;
    Msort(a,l,mid);
    Msort(a,mid+1,r);
    Merge(a,l,mid,r);
}
void mergesort(int *a)
{
    Msort(a,0,n-1);
}
8)基数排序

实现多关键字排序有两种常用的方法

最高位优先

最低位优先

image-20191230190811448

image-20191230190819363

image-20191230190827905

若关键字有d位,则需要进行d次分配手机

基数排序是稳定的排序方法

各种排序的比较:

image-20191230191023001