第三个一千行+500行总结-数据结构C复习--知识点总结3--七到九章

第七章 (接知识点总结2) 图

图的遍历:

//深度优先搜索
#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]                     dist[j] = dist[u]+g.edges[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                 high=mid-1;/*未找到,则继续在前半区间进行查找*/
            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 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);
}
 

你可能感兴趣的:(数据结构与算法--用C语言描述,算法,数据结构)