图的遍历:
//深度优先搜索
#define OK 1
#define True 1
#define Error -1
#define False 0
typedef enum{DG, DN, UDG. UDN}Graph;
int visited[MAX];
//Graph代表图的一种存储结构比如邻接表,邻接矩阵
void TranverseGraph(Graph g){
int vi;
for(vi = 0; vi < g.vernum; vi++)
visited[vi] = False;
for(vi = 0; vi < g.vernum; vi++){//连通图那么此操作执行一次
if(!visited[vi])
DepthFirstSearch(g, vi);
}
}
void DepthFirstSearch(Graph g, int v0)
{
visit(v0);
visited[v0] = True;
w = FirstAdjVertex(g, v0);
while(w != -1){
if(visited[w])
DepthFirstSearch(g, w);
w = NextAdjVertex(g, v0, w);
}
}
深度遍历总结:总之就是一直找邻接点,找到一个那么找这个结点的下一个邻接点,找不到就换...
对于邻接表:
void DepthFirstSearch(AdjList g, int v0){
visit(v0);
visited[v0] = True;//这里的True是宏定义,bool类型的为true
ArcNode *p;
p = g.vertex[v0].firstarc;
while(p){
if(!visited[p->adjvex])
DepthFirstSearch(g, p->adjvex);
p = p->nextarc;
}
}
图的遍历--广度优先搜索
void BreadFirstSearch(Graph g int v0){
visit(v0);
visited[v0] = True;//标志数组,证明该结点已经访问过
InitQueue(&Q);
EnterQueue(&Q, v0);
while(!IsEmpty(Q)){
DeleteQueue(&Q, &v);
w = FirstAdjVertex(g, v);
while(w != -1){
if(!visited[w]){
visit(w);
visited[w] = True;
EnterQueue(&Q, w);
}
w = NextAdjVertex(g, v, w);
}
}
}
广度遍历总结:就是一层一层的访问,访问完这一个结点衍生出去路径为1的结点,在走下一层...
图的应用
//求图中两个节点的简单路径
int pre[];
void one_path(Graph *G, int u, int v)
{
//找到一条从u到v结点的简单路径
int i;
pre = (int *)malloc(G->vexnum * sizeof(int));
for(i = 0; i < G->vernum; i++)
pre[i] = -1;//未访问标志
pre[u] = -2;//访问了,无前驱
DSF_path(G, u, v);
free(pre);
}
int DSF_path(Graph *G, int u, int v){
int j;
for(j = FirstAdj(G, u); j >= 0; j = nextadj(G, u, j)){
if(pre[j] == -1){
pre[j] = u;
if(j == v)
{
print_path(pre, v);
return 1;
}
else if(DFS_path(G, j, v))
return 1;
}
}
return 0;
}
生成树:一个连通图的生成树是一个极小联通子图, 含有全部顶点,只有构成一棵树的n-1条边
最小生成树:各边代价之和最小的生成树
Prime算法:
思想:点集分为两个U,V-U, 分别(U)存树上的结点和(V-U)去掉U中结点的剩余结点;从V-U种选择代价最小的边
并入边集,直到U=V.这一算法不会构成回路, 因为并入的边始终是邻接点分布在U和V-U中.
//邻接矩阵
#define MAXV ...
typedef struct{
int no;//顶点编号
InfoType info;
}VertexType;
typedef struct{
int edges[MAXV][MAXV];//邻接矩阵
int n, e;//顶点数量, 边数
VertexType vexs[MAXV];//存放结点信息
}MatGraph;
#define M 32767
void Prime(MatGraph g, int v){
int lowcost[MAXV];
int min;
int closet[MAXV];
int i, j ,k;
for(i = 0; i < g.n; i++){
lowcost[i] = g.edges[v][i];
closet[i] = v;
}
for(i = 1; i < g.n; i++){
min = M;
for(j = 0; j < g.n; j++){
if(lowcost[j] != 0 && lowcost[j] < min){
min = lowcost[j];
k = j;
}
}
printf("边(%d, %d)权为: %d", v, k, lowcost[k]);
lowcost[k] = 0;
for(j = 0; j < g.n; j++){
if(lowcost[j] != 0 && lowcost[j] > g.edges[k][j])
{
lowcost[j] = g.edges[k][j];
closet[j] = k;
}
}
}
}
Kruskal
总结:对所有边的权重值按照升序排列,依次选取权重较小的边,
确保不构成回路(选中的边的所有结点不能重复出现在新加入的一条边中)
#define 32367
typedef struct{
int u;
int v;
int w;
//边的始点, 重点, 权重
}Edge;
void Kruskal(MatGraph h){
int i, j, u1, v1, sn1, sn2, k;
int vset[MAXV];
Edge E[MaxSize];
k = 0;//指示数组E的下标
//对于边(权重)初始化
for(i = 0; i < g.n; i++){
for(j = i + 1; j < g.n; j++)//上三角
if(g.edge[i][j] != M)//M = 32367
{
E[k].u = i; E[k].v = j;
E[k].w = g.edge[i][j]; k++;
}
}
//冒泡排序最简单
Sort(E, edge); //升序, 权值递增
for(i = 0; i < g.n; i++)//初始化辅助数组
vset[i] = i;
k = 1;//k:当前构造生成树的第几条边, 初始值为1
j = 0;//E中边的下标
while(k < g.n){
u1 = E[j].u;
v1 = E[j].v;
sn1 = vset[u1];
sn2 = vset[u2];
if(sn1 != sn2){
printf("(%d, %d): %d", u1, v1, E[j].w);
k++;
for(i = 0; i < g.n; i++){
if(vset[i] == sn2)
vset[i] = sn1;//改结点相同, 表示这两个结点已经形
//通路, 避免成环
}
j++;
}
}
}
拓扑排序
原理: 对于邻接矩阵
找入度为零的结点i, 即列为零的结点,标记, 将其删除, 删除邻接边即将序号i的行置零 , 重复...
邻接表
加一个辅助数组记录每个结点的入度; 入度为0, 入栈;
栈非空, 出栈, 删除邻接边;
即辅助数组邻接点的总数count-1;
//邻接表定义如下
typedef struct ANode{
int adjvex; // 该边的终点编号
struct Anode *nextarc;
}ArcNode;
typedef struct{
Vertex data;
int count;//辅助数组, 存放入度
ArcNode *firstarc;
}VNode;
typedef struct{
VNode adjlist[MAV];
int n, e;
}AdjGraph;
拓扑排序
//基于邻接表
void TopSort(AdjGraph *G){
int i, j;
int st[MAXV], top = -1;//栈的指针
ArcNode *p;
for(i = 0; i < G->n;i++){//入度置初值为0
G->adjlist[i].count = 0;
}
for(i = 0; i < G->n; i++){//求所有顶点的入度
p = G->adjlist[i].firstarc;
while(p!=NULL){
G->adjlist[p->adjvex].count++;
p = p->nextarc;//邻接表
}
}
for(i = 0; i < G.n; i++){//入度为零进栈
if(G->adjlist[i].count == 0){
top++;
st[top] = i;
}
}
while(top != -2){
i = st[top]; top--;//出栈一个顶点i
printf("%d", i);
p = G->adjlist[i].firstarc;
while(p != NULL){
j = p->adjvex;//找出p的邻接点,入度-1
G->adjlist[j].count--;
if(G->adjlist[j].count == 0){//入度为0入栈
top++;
st[top] = j;
}
p = p->nextarc;
}
}
}
AOE网--边表示活动的网
唯一入度为0的顶点为源点,唯一出度为0的顶点为汇点
源点到汇点的最长路径长度为整个工程任务所需时间,称为关键路径
关键路径上的活动称为关键活动.
//迪杰斯特算法 看PPT,难度较大
void Dijkstra(MatGraph g, int v){ // v is the original dot.
int dist[MAXV], path[MAXV]; //dist is to memorize the weighted length of the path.
//path is used to memorize the node of the path
int s[MAXV]; //to mark the node whether have been used.
int mindist, i, j;
for(i = 0; i < g.n; i++){
dist[i] = g.edges[v][i]; //Firstly store the weighted length of the related edge.
//If it is not relevant to it, put infinite on it. If it is itself, put 0 on it
s[i] = 0;//s[] is cleared.
if(g.edge[v][i] < INF)//如果v和i邻接那么赋值i的邻接点为v
path[i] = v;
else
path[i] = -1;//path is used to memorize the node before the latest
//if it is -1, proven there is no path.
s[v] =1;
for(i = 0; i < g.n; i++){//找出路径长度最小顶点u
mindis = INF;
for(j = 0; j < g.n; j++)//Find out the shortest length of the node.
if(s[j] == 0 && dist[j] < mindis){
u = j;
mindis = dist[j];
}
s[u] = 1;//node u is added into s//u has been used
for(j = 0; j < g.n; j++)
if(s[j] == 0)//s[j]is not used
if(g.edge[u][j]
path[j] = u;
}
}
}
Dispath(dist, path, s, g.n, v);//printf
}
对于多源点最短路径问题
//佛洛里得算法 难度大,看ppt
void Floyd(MatGraph g){
int A[MAXVEX][MAXVEX];//记录路径长度
int path[MAXVEX][MAXVEX];//记录路径
int i, j, k;
for(i = 0; i < g.n; i++)
for(j = 0; j < g.n; j++){
A[i][j] = g.edges[i][j];
if(i!= j && g.edges[i][j] < INF)
path[i][j] = i;
else
path[i][j] = -1;//自环或者不存在邻接边
}
for(k = 0; k < g.n; k++){//O(n^3)
for(i = 0; i < g.n; i++)
for(j = 0; j < g.n; j++)
if(A[i][j] > A[i][k]+A[k][i]){
//i, j 之间有两种形式, i直接到j;
//或者i经过k到达j
A[i][j] = A[i][k]+A[k][i];
path[i][j] = path[k][j];
//修改最短路径经过k
}
}
}
第七章总结:图是最复杂的线性表.分类很多根据弧的有向与否,连通性,边数量,权....
储存分为边集合(邻接矩阵), 链接的方式(邻接表,十字链表,邻接多重表)
图的遍历分为深度遍历和广度优先遍历,前者递归,后者队列!!!
应用!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1
概念:列表(python,就相当于数组),主关键词,次关键字
查找:分为动态和静态查找 前者仅仅是查找, 后者插入找不到的元素或者删除已经查到的元素
基于线性表的查找
// 1.顺序查找法
//思想:所给的关键字和表中元素的关键字逐个比较
定义如下:
#define SIZE 20
typedef struct{
int key;
int other_data;
}RecordType;
typedef struct{
RecordType r[SIZE];//r[0]为工作单元
int length;
}RecordList;
分为:设置监视哨和不设监视哨
监视哨:r[0]防止越界
int SeqSearch(RecordList l, KeyType k)
//在顺序表l中顺序查找其关键字等于k的元素,若找到,则函数值为该元素在表中的位置,否则为0
{
int i;
l.r[0].key=k;
i=l.length;
while (l.r[i].key!=k) i--;
return(i);
}
int SeqSearch(RecordList l, KeyType k)
//不用"监视哨"法,在顺序表中查找关键字等于k的元素
{
int i;
i=l.length;
while (i>=1&&l.r[i].key!=k) i--;
if (i>=1)
return(i);
else
return (0);
}
//2.折半查找法
要求:顺序储存结构(不能链表!!!),按照关键字大小有序排列(正序和逆序)
思想:利用mid=(high+low)/2(整数). 判断条件:low<=high;
int BinSrch(RecordList l, KeyType k)
/*在有序表l中折半查找其关键字等于k的元素,若找到,则函数值为该元素在表中的
位置*/
{
int low,high,mid;
low=1;
high=l.length;/*置区间初值*/
while( low <= high)
{
mid=(low+high) / 2;
if (k==l.r[mid]. key)
return (mid);/*找到待查元素*/
else
if (k
else
low=mid+1;/*继续在后半区间进行查找*/
}
return (0);
}
//3.分块查找法
要求:列表分为子表,最后一个子表长度可以不满;索引表每个索引对应每个块(子表)的起始位置,记录每个块的最大(最小)
的关键字;索引表按照关键字有序排列
基于树的查找法
二叉排序树定义:元素大小关系:左子树<根<右子树,递归定义
结构定义:
typedef struct node{
int key;
struct node *lchild, *rchild;
}BSTNode, *BSTree;
插入:
递归算法
void InsertBST(BSTree *bst, KeyType key)
/*若在二叉排序树中不存在关键字等于key的元素,插入该元素*/
{
BSTree s;
if (*bst == NULL)/*递归结束条件*/
{
s=(BSTree)malloc(sizeof(BSTNode));/*申请新的结点s*/
s-> key=key;
s->lchild=NULL;
s->rchild=NULL;
*bst=s;
}
else
if (key < (*bst)->key)
InsertBST(&((*bst)->lchild), key);/*将s插入左子树*/
else
if (key > (*bst)->key)
InsertBST(&((*bst)->rchild), key); /*将s插入右子树*/
}
非递归: !!!!!!!!!!!!!!!
int InsertBST(BSTree *bst, KeyType K)
/*若在二叉排序树中不存在关键字等于key的元素,插入该元素*/
{
BSTree f, q, s;
s=(BSTree)malloc(sizeof(BSTNode));
s->key = K;
s->lchild = NULL;
s->rchild = NULL;
//空树直接赋值
if ( *bst == NULL )
{
*bst = s;
return 1;
}
//指针跟踪技术
f = NULL;
q = *bst;
while( q )
{
if ( q->key == K )
return 0;
if( K < q->key ) //小于走左边
{
f = q;
q = q->lchild;
}
else
{
f = q;
q = q->rchild;
}
}//走到头仍然没找到就插入
if ( K < f->key )
f->lchild = s;
else
f->rchild = s;
return 1;
}
创建二叉排序树:
void CreateBST(BSTree *bst)
/*从键盘输入元素的值,创建相应的二叉排序树*/
{
KeyType key;
*bst=NULL;
scanf("%d", &key);
while (key!=ENDKEY) /*ENDKEY为自定义常量*/
{
InsertBST(bst, key);
scanf("%d", &key);
}
}
查找:
递归算法
BSTree SearchBST(BSTree bst, KeyType key)
//在根指针bst所指二叉排序树中,递归查找某关键字等于key的元素,若查找成功,返回指向该元素结点指针,否则返回空指针
{
if (!bst)
return NULL;
else if (bst->key == key)
return bst;//查找成功
else if (bst->key > key)
return SearchBST(bst->lchild, key);//在左子树继续查找
else
return SearchBST(bst->rchild, key);//在右子树继续查找
}
二叉排序树的删除略显复杂:
1.不存在,不动
2.存在
2.1.叶子节点:直接删,free掉
2.2只有左右子树一支:孩子改到删去位置(孩子变为双亲),free
2.3.左右孩子都有:
2.3.1处理1:
利用中序遍历算法找到将要删除结点p的直接前驱s,p左子树变为其双亲的左孩子,右子树变为其前驱s的右孩子
2.3.2处理2:
直接删除结点p,p的前驱s代替p,free(s),s的左孩子为s的双亲的右孩子,p的右孩子为s的右孩子
!!!
BSTNode * DelBST(BSTree t, KeyType k) /*在二叉排序树t中删去关键字为k的结点*/
{
BSTNode *p, *f,*s ,*q;
p=t;
f=NULL;
while(p) /*查找关键字为k的待删结点p*/
{
if(p->key==k ) break; /*找到则跳出循环*/
f=p; /*f指向p结点的双亲结点*/
if(p->key>k)
p=p->lchild;
else
p=p->rchild;
}
if(p==NULL) return t; /*若找不到,返回原来的二叉排序树*/
if(p->lchild==NULL) /*p无左子树*/
{
if(f==NULL)
t=p->rchild; /*p是原二叉排序树的根*/
else
if(f->lchild==p) /*p是f的左孩子*/
f->lchild=p->rchild ; /*将p的右子树链到f的左链上*/
else /*p是f的右孩子*/
f->rchild=p->rchild ; /*将p的右子树链到f的右链上*/
free(p); /*释放被删除的结点p*/
}
else /*p有左子树*/
{
q=p;
s=p->lchild;
while(s->rchild) /*在p的左子树中查找最右下结点*/
{
q=s;
s=s->rchild;
}
if(q==p)
q->lchild=s->lchild ; /*将s的左子树链到q上*/
else
q->rchild=s->lchild;
p->key=s->key; /*将s的值赋给p*/
free(s);
}
return t;
}
平衡二叉排序树:左子树右子树高度绝对值之差小于等于1
插入算法:LL,RR,LR,RL型
结构定义中含平衡因子代码如下:
void ins_AVLtree(AVLTree *avlt , KeyType K)
/*在平衡二叉树中插入元素k,使之成为一棵新的平衡二叉排序树*/
{
AVLTNode *S;
AVLTNode *A,*FA,*p,*fp,*B,*C;
S=(AVLTree)malloc(sizeof(AVLTNode));
S->key=K;
S->lchild=S->rchild=NULL;
S->bf=0;
if (*avlt==NULL)
*avlt=S;
else
{
/* 首先查找S的插入位置fp,同时记录距S的插入位置最近且
平衡因子不等于0(等于-1或1)的结点A,A为可能的失衡结点*/
A=*avlt; FA=NULL;
p=*avlt; fp=NULL;
while (p!=NULL)
{
if (p->bf!=0)
{
A=p; FA =fp;
}
fp=p;
if (K < p->key)
p=p->lchild;
else
p=p->rchild;
}
/* 插入S*/
if (K < fp->key)
fp->lchild=S;
else
fp->rchild=S;
/* 确定结点B,并修改A的平衡因子 */
if (K < A->key)
{
B=A->lchild;
A->bf=A->bf+1;
}
else
{
B=A->rchild;
A->bf=A->bf-1;
}
/* 修改B到S路径上各结点的平衡因子(原值均为0)*/
p=B;
while (p!=S)
{
if (K < p->key)
{
p->bf=1;
p=p->lchild;
}
else
{
p->bf=-1;
p=p->rchild;
}
/* 判断失衡类型并做相应处理 */
if (A->bf==2 && B->bf==1) /* LL型 */
{
B=A->lchild;
A->lchild=B->rchild;
B->rchild=A;
A->bf=0;
B->bf=0;
if (FA==NULL)
*avlt=B;
else
if (A==FA->lchild)
FA->lchild=B;
else
FA->rchild=B;
}
else
if (A->bf==2 && B->bf==-1) /* LR型 */
{
B=A->lchild;
C=B->rchild;
B->rchild=C->lchild;
A->lchild=C->rchild;
C->lchild=B;
C->rchild=A;
if (S->key < C->key)
{
A->bf=-1;
B->bf=0;
C->bf=0;
}
else
if (S->key >C->key)
{
A->bf=0;
B->bf=1;
C->bf=0;
}
else
{
A->bf=0;
B->bf=0;
}
if (FA==NULL)
*avlt=C;
else
if (A==FA->lchild)
FA->lchild=C;
else
FA->rchild=C;
}
else
if (A->bf==-2 && B->bf==1) /* RL型 */
{
B=A->rchild;
C=B->lchild;
B->lchild=C->rchild;
A->rchild=C->lchild;
C->lchild=A;
C->rchild=B;
if (S->key
{
A->bf=0;
B->bf=-1;
C->bf=0;
}
else
if (S->key >C->key)
{
A->bf=1;
B->bf=0;
C->bf=0;
}
else
{
A->bf=0;
B->bf=0;
}
if (FA==NULL)
*avlt=C;
else
if (A==FA->lchild)
FA->lchild=C;
else
FA->rchild=C;
}
else
if (A->bf==-2 && B->bf==-1) /* RR型 */
{
B=A->rchild;
A->rchild=B->lchild;
B->lchild=A;
A->bf=0;
B->bf=0;
if (FA==NULL)
*avlt=B;
else
if (A==FA->lchild)
FA->lchild=B;
else
FA->rchild=B;
}
}
}
}
icoding例题:
AVL添加
平衡二叉树,是一种二叉排序树,其中每个结点的左子树和右子树的高度差至多等于1。
它是一种高度平衡的二叉排序树。现二叉平衡树结点定义如下:
typedef struct node
{
int val;
struct node *left;
struct node *right;
struct node *parent;
int height;
} node_t;
//请实现平衡二叉树的插入算法:
//向根为 root 的平衡二叉树插入新元素 val,成功后返回新平衡二叉树根结点
node_t *avl_insert(node_t *root, int val);
#include
#include
#include "avl.h"
int get_height(node_t *p){
if(!p)
return 0;
else
return p->height;
}
node_t* avl_insert(node_t *root, int val){
//首先清晰字母定义;
//val新插入结点元素值,height高度!!!
//定义查找过程中出现的距离插入结点最近的平衡因子不为零的结点A
//定义A的孩子结点为B,需要旋转的结点
//定义插入节点为s,s的值为val
//平衡因子:左子树减去右子树深度
node_t *s, *A, *B, *C, *p, *fp;
//依次:插入点, 失衡点(也可能是旋转点),旋转点,旋转点(也可能是插入点=s),动点,跟踪点
int i, k;//平衡因子
s = (node_t *)malloc(sizeof(node_t));
if(!s) return NULL;
s->val = val;
s->left = s->parent = s->right = NULL;
s->height = 1;
//类似于指针跟踪技术,p为动指针,A为跟踪指针
A = root; A->parent = NULL;
p = root; p->parent = NULL;
//找出A
if(!p)
root = s;
else{
while(p){
//先找出最近的A->height!=0的结点, 就是最后的失衡点
i = get_height(p->left) - get_height(p->right);
if(i){
A = p;
A->parent = p->parent;
}
//fp跟踪p,因为下一步p会往下移动,p最终指向s的那一层
fp = p;
if(val < p->val)
p = p->left;
else
p = p->right;
}//p最终指向NULL就推出循环
}
//插入, 此时fp是p的前一个结点,p指向空
if(val < fp->val)
fp->left = s;
else
fp->right = s;
//确定旋转结点B,修改A的平衡因子
if(val < A->val)
B = A->left;
else
B = A->right;
A->height++;
//修改路径B到s的高度, B在A的下一层
p = B; // p最终指向s, 之前指向的是s这一层,但是是空
while(p != s){
p->height++;
if(val < p->val)
p = p->left;
else
p = p->right;
}
//最终s的高度没有++的
//调整完高度就修改结点和指针, 首先需要判断失衡类型
//分为LL,LR,RR,RL
//结点A,B平衡因子在修改指针的过程中会变化,但是路径上的结点不会
//指针修改包括s结点指针和原来双亲结点指针
i = get_height(A->left) - get_height(A->right);
k = get_height(B->left) - get_height(B->right);
if(i == 2 && k == 1){//LL
//B作为旋转结点
//先改结点指针, 此时s插入在B的左子树下, 原来可以认为B左右子树,A右子树均为空
A->left = B->right;
B->right = A;
//考虑原来A结点的指针,改成B后相应的指针也要改变,下面同理
if(A->parent == NULL)
root = B;
else if(A->parent->left == A)
A->parent->left = B;
else
A->parent->right = B;
}
else if(i == -2 && k == -1){//RR
A->right = B->left;
B->left = A;
if(A->parent == NULL)
root = B;
else if(A->parent->left == A)
A->parent->left = B;
else
A->parent->right = B;
}
else if(i == 2 && k == -1){//LR
//此时认为C的左右子树空,B逆时针旋转,A顺时针旋转, s插入C的左子树或者右子树
//如果C结点也是空,也就是说B右子树空,那么直接插入C=s为B右子树,此时A右子树也是空的
C = B->right;
B->right = C->left;
A->left = C->right;
C->left = B;
C->right = A;
if(A->parent == NULL)
root = C;
else if(A->parent->left == A)
A->parent->left = C;
else
A->parent->right = C;
}
else if(i == -2 && k == 1){//RL
//和LR一样,画图来看就好
C = B->left;
A->right = C->left;
B->left = C->right;
C->left = A;
C->right = B;
if(A->parent == NULL)
root = C;
else if(A->parent->left == A)
A->parent->left = C;
else
A->parent->right = C;
}
return root;
}
计算式查找法:hash(python的字典就是这么存的)
//hash:
1.数字分析法:选择合适位数的分布均匀的关键字
2.平方取中法:求关键字平方值,取中间,重复概率低
3.分段叠加法:折叠法,移位法
4.除留余数法:取余除数为小于等于表长的最大素数
5.伪随机数法:电脑生成伪随机数
处理冲突!!!!:
1.开放地址法(再散列法):
1.1.线性探测再散列:di = 1,2,3......
1.2.二次探测再散列:di = 1^2, -1^2, 2^2, -2^2,......
1.3.伪随机探测再散列: ...
2.再哈希法
3.链地址法:!!!
4.建立公共溢出区:分为基本表和溢出表
icoding:
哈希表创建
typedef enum{
HASH_OK,
HASH_ERROR,
HASH_ADDED,
HASH_REPLACED_VALUE,
HASH_ALREADY_ADDED,
HASH_DELETED,
HASH_NOT_FOUND,
} HASH_RESULT;
typedef struct __HashEntry HashEntry;
struct __HashEntry{
union{
char *str_value;
double dbl_value;
int int_value;
} key;
union{
char *str_value;
double dbl_value;
int int_value;
long long_value;
void *ptr_value;
} value;
HashEntry *next;
};
struct __HashTable{
HashEntry **bucket;
int size;
HASH_RESULT last_error;
};
typedef struct __HashTable HashTable;
// 创建大小为hash_size的哈希表,创建成功后返回HashTable类型的指针,否则返回NULL。
HashTable *create_hash(int hash_size);
哈希表相关说明:
HASH_RESULT 类型为相关函数的返回类型
HashEntry 为哈希表所保存元素(即键值对 《key, value》)类型
HashTable 为哈希表,其中 bucket 指向大小为size的、元素类型为 HashEntry*的指针数组
哈希表采用链地址法处理冲突
请实现 create_hash 函数,创建指定大小的哈希表。
#include
#include
#include "hash.h"
HashTable* create_hash(int size){
HashTable *r;
r = (HashTable *)malloc(sizeof(HashTable));
if(r == NULL) return NULL;
r->bucket = (HashEntry **)malloc(size * sizeof(HashEntry *));
if(r->bucket == NULL){
free(r);
return NULL;
}
r->size = size;
r->last_error = HASH_OK;
return r;
}
添加:
向哈希表中添加元素,其中键类型为char*, 元素类型为int。
HASH_RESULT hash_add_int(HashTable * table, const char * key, int value);
哈希表相关说明:
HASH_RESULT 类型为相关函数的返回类型
HashEntry 为哈希表所保存元素(即键值对 《key, value》)类型
HashTable 为哈希表,其中 bucket 指向大小为size的、元素类型为 HashEntry*的指针数组
哈希表采用链地址法处理冲突
请实现 hash_add_int 函数,向哈希表中添加元素,其中键类型为char*, 元素类型为int。
在添加过程中,如果要添加的键值key已在哈希表中,且对应的值value也已存在,则函数返回 HASH_ALREADY_ADDED;
如果要添加的键值key已在哈希表中,但对应的值value不同,函数将value值更新到哈希表中,之后返回 HASH_REPLACED_VALUE
如果要添加的键值key不在哈希表中,则函数创建 HashEntry 类型,并将其加入到哈希表中,且函数返回 HASH_ADDED。
本题所用的哈希函数如下:
long hash_string(const char *str)
{
long hash = 5381;
int c;
while (c = *str++)
hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
if(hash < 0)
hash *= -1;
return hash;
}
#include
#include "stdlib.h"
#include "hash.h"
#include
HASH_RESULT hash_add_int(HashTable *table, const char *key, int value ){
HashEntry *p;//指的是每一个键值对
int h = hash_string(key) % table->size;//保存哈希函数返回值
//int类型也可以
// !h %= table->size; 编译器奇怪的不通过.....
//该关键字对应的哈希表中键值对不存在, 分配节点存入
if(!table->bucket[h]){
p = (HashEntry *)malloc(sizeof(HashEntry));
if(!p) return HASH_ERROR;
p->key.str_value = (char *)malloc(strlen(key));
if(!p->key.str_value){
free(p);
return HASH_ERROR;
}
//!!!!字符串拷贝
strcpy(p->key.str_value, key);
p->value.int_value = value;
//最好还是置空
p->next = NULL;
table->bucket[h] = p;
return HASH_ADDED;
}
//关键字对应的哈希表中该位置存在键值对,判断重复或者冲突
p = table->bucket[h];
while(p){
//关键字相同
if(strcmp(key, p->key.str_value)==0){
//判断值
if(p->value.int_value == value){
return HASH_ALREADY_ADDED;
}
else{
p->value.int_value = value;
return HASH_REPLACED_VALUE;
}
}
//链地址法
else{
if(p->next)
p = p->next;
else
break;
}
}
//循环完成后
//p指向最后一个结点
HashEntry *q;//q接到p后面
q = (HashEntry *)malloc(sizeof(HashEntry));
if(!q) return HASH_ERROR;
q->key.str_value = (char *)malloc(strlen(key));
if(!q->key.str_value){
free(q);
return HASH_ERROR;
}
strcpy(q->key.str_value, key);
q->value.int_value = value;
q->next = NULL;
p->next = q;
return HASH_ADDED;
}
第八章总结:
查找机制: 基于线性结构:静态查找表,基于比较的顺序检索和折半检索(有点像快排)
基于树形结构:动态查找
哈希计算式查找: 根据关键字计算存储地址
二叉排序树:值域左子树 < 根 < 右子树
其构建:每次从根节点开始比较,顺次比较插入
对于二叉排序树中序遍历得到递增序列
查找:比较次数不超过树的深度,平均查找长度O(log2(n)).
平衡二叉树...(AVL)
Hash法...
排序:重点在于对于记录的关键字进行排序,得到按关键字有序记录序列
分为:
A.内部排序: 排序过程在内存中进行
B.外部排序: 待排序记录数据量过大,需要借助外部存储设备
排序的稳定性:排序后有相同关键字的记录顺序不变就是稳定的排序
插入类排序:
1.直接插入排序:将新加入的记录的关键字与之前的记录关键字从后往前比较,
若较小,则向前比较,同时进行比较的记录后移一个位置,直到找到小于等于的关键字,插入在其后.
实例代码如下: (表内操作)
void InsSort(int r[], int length){//r可以设置为结构数组,这里认为是数组
int i,j;
for(i = 2; i < length; i++){ // i=2开始,i=1为第一个元素,认为是子表,i=0设置为监视哨
r[0] = r[i];//将待插入记录存到监视哨中,临时保存
j = i - 1; //i为初始待插入记录位置,i-1为需要比较的记录位置
while(r[0] < r[j]){
r[j+1] = r[j];
j--;
}
r[j+1] = r[0];
}
}
优点:算法简单,适用于记录数目较少且基本有序
时间复杂度:O(n^2).
2.折半插入排序:类似于快排
示例代码如下:
void BinSort(int r[], int length){
int i, x, j;
int low, high, mid;
for(i = 2;i <= length; i++){
x = r[i];
low = 1;
high = i - 1;
while(low < high){//Attention!不取等,书上是错的
mid = (low + high) / 2;
if(x < r[mid])
high = mid - 1;
else
low = mid + 1;
}
for(j = i - 1; j >= low; j--)
r[j+1] = r[j];
r[low] = x;
}
}
时间复杂度:O(n^2).
需要比较的次数最大为其折半判定树的深度log2(n)
3.希尔排序:排序结果,基本有序;又称缩小增量排序;将关键字序列分为若干个子序列,对子序列插入排序
void f1(int r[], int length, int d){//d为这一轮子序列长度(增量)
int i, j;
for(i = 1+d; i <= length; i++){
if(r[i] < r[i-d]){
r[0] = r[i];
for(j = i - d; j > 0 && r[j] > r[0]; j -= d){
r[j + d] = r[j];
}//如果子序列后者的记录关键字比前小,就复制前者到后者
r[j + d] = r[0];//复制要交换的一个到适合的位置
}
}
}
void f2(int r[], int length, int d[], int n){
for(i = 0; i < n; i++)//d[]为增量数组,n为该数组长度 d[n-1] == 1;
f1(r, length, d[i]);
}
时间复杂度:O(n^1.5).
算法不是稳定的 .
交换类排序:
1.冒泡排序(相邻比序法):反复扫描记录序列,依次交换逆序记录的位置
void BubbleSort(int r[], int n){
bool change = true;
int i,j;
int x = 0;
for(i = 1; i < n && change; i++){
change = false;
for(j = 1; j <= n - i; j++){//一趟排序后最大的定了,在最后
if(r[j]>r[j+1])
{
x = r[j];
r[j] = r[j+1];
r[j+1] = x;
change = true;
}
}
}
}
//下面这种简单些:上升法,不带标记
void BubbleSort(int r[], int n){
int i, j, k;
for(i = 0; i < n; i++){
for(j = n - 2; j >= i; j--){
if(r[j] > r[j+1]){
k = r[j];
r[j] = r[j+1];
r[j+1] = k;
}
}
}
}
时间的复杂度:O(n^2).
2.快排:原理:一次性可以消除多个逆序来减少耗费时间
找到一个划分元,关键字小的移到前面,大的移到后面,递归在子序列中找出划分元.直到子表长度小于等于1
void QKSort(int r[], int low. int high){
if(low < high){
pos = QKPass(r, low, high);//再次快排
QKSort(r, low, pos -1);
QKSort(r, pos +1, high);
}
}
一趟快速排序算法:
int QKPass(int r[], int low, int high){
int x;
while(low < high){
while(low < high && r[high] > x)
high--;
if(low < high){
r[low] = r[high];
low++;
}
while(low < high && r[low] < x)
low++;
if(low < high){
r[high] = r[low];
high--;
}
}
r[low] = x;
return low;
}
时间复杂度:O(nlog2(n))
选择类排序:
1.简单选择排序:直接从数组中选择最小的记录和第一个记录交换位置,循环
void SelectSort(int r[], int b){
int i, j, k;
int x;
for(i = 1; i < n; i++){
k = i;
for(j = i+1; j <= n; j++){
if(r[j] < r[k])//选择最小的记录,得到在数组中的位置
k = j;
}
if(k != i){
x = r[i];
r[i] = r[k];
r[k] = x;
}//交换位置
}
}
时间复杂度:O(n^2).
2.树形选择排序(锦标赛排序):与简单选择排序不同点是,占用空间更多,保留了之前的比较结果
每一个记录看作叶子节点,两两比较,选出最小的为双亲,进一步递归向上,找出根,比较成功后,
该记录对应的叶子节点置为无穷;
进一步两两比较重复上述过程,直到记录全部输出
时间复杂度:O(nlog2(n)).
3.堆排序:排序过程中把向量中储存的数据看作一颗完全二叉树来进行操作
重建堆:
大堆,筛选最大的元素出去,然后最后的元素补根节点,调整堆使最大的元素在最上面
void sift(Type r[], int k, int m){
//r[k...m]是以r[k]为根的完全二叉树,调整r[k]使之满足堆的性质
int i, j, t, x;
t = r[k];
x = r[k].key;
i = k;
j = 2 * k;//j指向t的左孩子
bool finished = false;
while(j <= m && !finished){
if(j + 1 <= m && r[j].key < r[j+1].key){
j++;
}//得到左右孩子中记录关键字较大的位置坐标
if(x >= r[j].key) //如果满足堆的性质,上面的比孩子大
finished = true;
else{
r[i] = r[j];
i = j;
j = 2 * i;
}
}
r[i] = t;
}
建初堆:
void crt_heap(Type r[], int n)
{
//对r[]建立堆, n为数组长度
int i;
for(i = n / 2; i >= 1; i--)//i指向最后一个非叶子节点
sift(r, i, n);
}
堆排序算法:
void HeapSort(Type r[], int n)
{
crt_heap(r, n);
for(i = n; i>= 2 ;--i){
r[0] = r[1];
r[1] = r[i];
r[i] = r[0];//最后一个元素和第一个元素交换位置,把最大的换到最后面去,以此达到升序排列x
sift(r, 1, i-1);
}
}
时间复杂度:O(nlog2(n)).
算法是不稳定的, 空间复杂度O(1) .
归并类排序:将两个或两个以上的有序表合并成一个表
两个有序子序列合并算法:
void Merge(Type r1[], int low, int mid, int high, Type r2[])
{
//r1[low...mid]和r1[mid+1,..high]分别按照关键字有序排列 ,合并存放在r2[]中
int i, j, k;
i = low;
j = mid + 1;
k = low;
while(i <= mid && j <= high){
if(r1[i].key <= r1[j].key)
r2[k++] = r[i++];
else
r2[k++] = r[j++];
}
while(i <= mid){
r2[k++] = r1[i++];
}
while(j <= high){
r2[k++] = r1[j++];
}
}
2-路归并排序的递归算法:
void MSort(Type r1[], int low, int high, Type r3[])
{
//r1[low...high]排序后放在r3[low...high] 中, r2为辅助空间
Type *r2;
int mid;
r2 = (Type *)malloc(sizeof(Type) * (high - low + 1));
if(low == high) r3[low] = r1[low];
//这个是递归最终退出条件
else{//r1前半段放到r2前半段中,同理对于后半段,再将r2合并排序
mid = (low + high) / 2;
MSort(r1, low, mid, r2);
MSort(r1, mid + 1, high, r2);
Merge(r2, low, mid, high, r3);
}
free(r2);
}
调用:
void MergeSort(Type r[], int n){
MSort(r, 1, n, r);
}
分配类排序:核心是分配和收集,利用关键字的优先级进行排序的思想
高位优先排序:比如桥牌,先比较花色在比较面值;比如学号,比较级,院,班,号;
低位优先排序: 链式基数排序
思想:基于"分配"和"收集"的操作, 将单关键字转化为多关键字排序
将链表作为存储结构, 待排序记录以指针相连,构成链表;
分配:按照关键字取值分配记录到链队列相应队列中,每个队列关键字取值相同
收集:按照关键字大小,从小到大,将队列首尾相接连接成一个链表;
重复上述步骤..
定义:
//待排序记录结点
typedef struct node{
int data;//比如说一个三位数
struct node *next;
}TNode;
//队列首尾指针
typedef struct{
node *front;
node *rear;
}TPointer;
//根据数组R[](已经存在元素),构建带头结点待排序记录链表
TNode *create_list(int R[], int n){
TNode *p, *ph;
//p为每一个存了记录的结点, ph为头结点
ph = (TNode *)malloc(sizeof(TNode));
if(!ph) return NULL;
ph->next = NULL;
int i;
for(i = 0; i < n; i++){
p = (TNode *)malloc(sizeof(TNode));
if(!p) return NULL;
p->data = R[i];
//头插法插入
p->next = ph->next;
ph->next = p;
}
return ph;//返回头结点
}
#define N 10
//分配算法,对三位数的记录序列按照关键字低位排序分配
void dispatch(TNode *ph, TPointer *Q, int d){
//ph存记录, Q队列:存指针,d根据关键字到那一趟取值不同
TNode *p = NULL;//作为辅助空间拆分ph
int i, idx;
//初始化Q
for(i = 0; i < N; i++){
Q[i].front = NULL;
Q[i].rear = NULL;
}
p = ph->next;
if(p){
ph->next = p->next;
p->next = NULL;
}//第一个记录被单独拆到p里面
while(p){
idx = p->data;
for(i = 0; i < d; i++)
idx = idx / N;
//第一趟排序,d = 0
idx = idx % N;
//根据关键字分配到Q中
if(Q[idx].front = NULL){
Q[idx].front = p;
Q[idx].rear = p;
}
else{//尾插法
Q[idx].rear->next = p;
Q[idx].rear = p;
}
p = ph->next;
if(p){//拆,直到拆完
ph->next = p->next;
p->next = NULL;
}
}
}
void collect(TNode *ph, TPointer *Q){
TNode *p;
int i;
//ph为头结点,最终可以传出去
for(i = 0; !Q[i].front; i++)
;//找出第一个队列中非空结点
ph->next = Q[i].front;
p = Q[i].rear;
i++;
for(; i < N; i++){
if(Q[i].front){
p->next = Q[i].front;
p = Q[i].rear;//注意的是Q[i]内部的结点是连接好的
}
}
p->next = NULL;
}
void list_sort(int *R, int n){
int i;
TNode *ph, *p;
TPointer Q[N];
int m = max(R, n);//最大关键字
ph = create_list(R, n);
for(i = 0; m; i++, m /= N){
dispatch(ph, Q, i);
collect(ph, Q);
}
for(i = 0, p = ph->next; p; p = p->next){
R[i] = p->data;//复制到数组最终输出
}
free(ph);
}