Skip to content

数据结构与算法

一、绪论

1.1 数据结构基本概念

1.2 算法和算法评价

数据结构三要素:逻辑结构,存储结构,数据的运算。 (描述的数据之间的关系以及数据在计算机中的存储方式)

逻辑结构:线性结构,非线性结构 存储结构:顺序存储,链式存储,索引存储,散列存储

算法五个特性:输入,输出,有穷性,确定性,可行性 好的算法应该具备:正确性,可读性,健壮性,高效性。 算法的效率评价:时间复杂度,空间复杂度


二、线性表

顺序表(数组)
链表:单链表,双列表,带头节点操作一致
操作受限的线性表:栈FILO,队列FIFO

广义表

head:取表头元素,去括号
tail:去表头元素,不去括号

链表代码题:一律使用带头节点的链表,方便操作

//结点结构体 
typedef struct LNode{
    int data;
    struct LNode *next;
}LNode,*List;

//初始化申请
List p = (List)malloc(sizeof(LNode));//链表概念,已是指针形式
LNode *p = (LNode*)malloc(sizeof(LNode));//节点概念,变量名定义指针
  • 逆置带头节点的单链表
List Reverse(List head)
{
    LNode *p,*q,*r;//三指针p q r
    p = head->next;
    q = p->next;
    p->next = NULL;

    while(q){
        //pqr位置按顺序排列
        r = q->next;
        //逆指
        q->next = p;
        //后移
        p = q;
        q = r;

        head->next = p;
    }
    return head;
}
  • 带头结点的单链表删除重复元素
void Delete(List &L)
{
    LNode *p = L->next,*q,*prep;

    while(p!=NULL)
    {
        prep = p;
        q = p->next;
        while(q!=NULL)
        {
            if(p->data==q->data){
                prep->next = q->next;
                free(q);
                q = prep->next;
            }
            else{
                prep=q;
                q=q->next;
            }
        }
        p=p->next;
    }
}
  • 合并递增有序的的A链表与B链表,形成递增有序的新链表C
List Merge(List &A,List &B)
{
    List C=(List)malloc(sizeof(LNode));
    //pc指针指向c尾结点
    LNode *pc=C;
    //pa,pb访问a,b的每个节点
    LNode *pa=A->next,*pb=B->next;

    //循环添加小元素
    while(pa&&pb){
        if(pa->data<=pb->data)
        {
            pc->next=pa;
            pc=pa;
            pa=pa->next;
        }
        else
        {
            pc->next=pb;
            pc=pb;
            pb=pb->next; 
        }
    }
    //其中一表已空,另外一表属于直接并入
    if(pa){
        pc->next=pa;
    }
    if(pb){
        pc->next=pb;
    }
    return C;
}

三、栈 队列 数组


四、串


五、树

二叉树的遍历

先序NLR 中序LNR 后序LRN 层序。
除了先序+后序,其他任意两个序列可以推出二叉树的形状。

线索二叉树

序列有先序,中序,后序。节点有左孩子指针和右孩子指针。
根据序列,若左孩子指针为空,左孩子指针指向前继节点,若右孩子为空,则右孩子指针指向后继节点。
序列首没有前继,左孩子指针指向NULL,序列尾没有后继,右孩子指针指向NULL。

平衡二叉树的插入删除调整

哈夫曼树(带权路径长度)

一堆带有权值的节点,构成一个森林
1 森林中选取权值最小的两个节点,组成一个树,树的根节点为两个节点权值和。(左小右大)
2 将树加入森林中,再选取根节点最小的两个树,形成新的树,树的根节点为两根节点之和,再加入森林
3 循环,直至所有节点加入,生成哈夫曼树

哈夫曼编码由哈夫曼树生成,左路为0,右路为1.

WPL=Σ(节点权值*节点哈夫曼树路径长度)/节点个数

树/森林 转二叉树

孩子兄弟表示法,左孩子,右兄弟。
二叉树的左子树是森林里某树的孩子
二叉树的右子树是森林里某树的兄弟

树的节点与度的计算

边数=节点数-1=n-1(除了根节点,每个节点有唯一被指)
度数总和等于边数:n-1=0* n0 +1* n1+2* n2...(度即是边)
  • 树的存储:顺序存储,链式存储

  • 稀疏矩阵的存储:三元组、单链表、十字链表

  • n个顶点的最小连通图:n-1条边

  • 图的表示:()为无向图,<>为有向图

  • 图的存储:邻接矩阵,邻接表存储

二叉树代码题:树用孩子兄弟表示法,指针域表示不同

//结点结构体
//若是孩子兄弟表示法表示树
//struct TNode *firstchild,*nextsibling;
typedef struct TNode{
    int data;
    struct TNode *lchild,*rchild;
}TNode,*Tree;

//初始化申请
Tree p = (Tree)malloc(sizeof(TNode));//树概念,已是指针形式
TNode *p = (TNode*)malloc(sizeof(TNode));//节点概念,变量名定义指针
  • 递归遍历

(代码题都是遍历的扩展,万变不离其中)

//NLR 前序遍历 
void TravelNLR(TNode *t)//t根节点指针
{
    if(t==NULL) return;

    printf("%d",t->data);
    TravelNLR(t->lchild);
    TravelNLR(t->rchild);
}

//LNR 中序遍历 
void TravelLNR(TNode *t)//t根节点指针
{
    if(t==NULL) return;

    TravelLNR(t->lchild);
    printf("%d",t->data);
    TravelLNR(t->rchild);
}

//LRN 后序遍历 
void TravelLRN(TNode *t)//t根节点指针
{
    if(t==NULL) return;

    TravelLRN(t->lchild);
    TravelLRN(t->rchild);
    printf("%d",t->data);
}
  • n个节点的完全二叉数组,建树
Tree Create(ElemType A[],int i){
    if(i>n) return NULL;//下标越界

    TNode *p = (TNode*)malloc(sizeof(TNode));//申请树节点
    p->data = A[i];
    p->lchild = Create(A,2*i);
    p->rchild = Create(A,2*i+1);

    return p;
}
  • 统计二叉树中度为1的结点的个数
void Count(TNode *t,int *num)
{
    if(t==NULL)  return;
    if((t->rchild==NULL&&t->lchild!=NULL)||(t->rchild!=NULL&&t->lchild==NULL))
        *num+=1;

    Count(t->lchild,num); 
    Count(t->rchild,num); 
}

Count(T,&num)
  • 二叉树左右子树交换
void Change(Tree &T)
{
    if(T==NULL) return;
    else{
        TNode *temp=T->lchild;
        T->lchild=T->rchild;
        T->rchild=temp;

        Change(T->lchild);
        Change(T->rchild);
    }
}

Change(T)
  • 二叉树输出每一个节点的深度
void NodeDepth(TNode *t,int h)
{
    if(t==NULL) return;
    //前序遍历
    printf("%d",h);
    NodeDepth(t->lchild,h+1);
    NodeDepth(t->rchild,h+1);
}

NodeDepth(T,1);
  • 兄弟孩子表示法的树输出每个叶子节点所在层数
typedef struct TNode{
    int data;
    struct TNode *firstchild,*nextsibling;
}TNode,*Tree;

void nodedepth(TNode *t,int h)
{
    if(t==NULL) return;
    //为叶子结点,直接输出
    if(t->firstchild==NULL) printf("%d %d",t->data,h);

    //左孩子,深度+1
    nodedepth(t->firstchild,h+1);
    //右兄弟,深度不变
    nodedepth(t->nextsibling,h);
}

六、图

  • 图的存储

    邻接矩阵与邻接表存储
    
  • 有向图的拓扑排序

    1找到入度为0的顶点,该点指向的其他点入度减一(擦去指向)
    2这些入度减一(擦去指向)的顶点中,选取入度为0的顶点
    重复,直到找不到入度为0的顶点。
    
  • 图的遍历

    深度优先DFS:从一个节点开始,向下层遍历,直到无法向下,回退至可向下遍历的点,直到所有节点被访问
    
    广度优先BFS:从一个节点开始,遍历它的所有邻接点,再从第一个邻接点开始,如此循环
    
  • 最小生成树(不唯一)

    Prim求最小生成树(顶点开始发散)
    
        1从顶点开始,选取权值最小的边,最小边顶点加入生成树。
        2再寻找生成树内权值最小的边,最小边顶点加入生成树,循环。
    
    Kruskal求最小生成树(加入最小边)
    
        1将所有的边按照权值从小到大排序
        2依次将边从小到大加入生成树
        3若加入的边使树形成回路,则舍去
        直至所有定点加入生成树
    

七、查找算法

  • 顺序查找,求ASL

  • 二分查找(折半查找)

    如果序列长度为奇数,则二分中间位置确定,为中间数,两侧等长
    如果序列长度为偶数,则二分中间位置为n/2取整,为小一边的数,两侧差1
    
  • 散列查找(线性探测法,平方探测,链地址法),求ASL

    线性探测:冲突后+1移位寻找空位
    平方探测:冲突后1^2,-1^2,2^2,-2^2,...以此类推
    链地址:冲突后,添加链表
    
    冲突一次,该数据的查找次数+1,初始查找次数为1
    ASL平均查找长度=sum(各数据查找次数)/数据个数
    

八、排序算法

  • 插入排序类

    直接插入:选取确定序列后的一个元素,向前寻找插入位置
    
    希尔排序:按照步长,分为多个小组进行直接插入排序
    
  • 交换排序类

    冒泡排序:从前向后冒,一趟确定一个
    
    快速排序:位置待定法,前后指针比较交换,一趟确定一个
    
  • 选择排序类

    简单选择排序:
    
        在待查找序列中选择最小的值,放入已确定序列末尾,一趟确定一个
    
    堆排序:
    
        大顶堆:每个节点的值都 大于 或等于其子节点的值,用于升序排列
        小顶堆:每个节点的值都 小于 或等于其子节点的值,用于降序排列
    
        1构建二叉树堆
        2按照大/小顶堆规律,从最底层最小子树开始,向上传递子树内最大/小值
        3最底层完成后,最底层子树规模扩大一层,再向上一层传递子树内最大/小值
        4传递后应该再调整小一层规模的子树,确保每个节点大于孩子
        5循环至整个二叉树层全包含,并且向下调整完成
    
  • 归并排序

    分而治之,大序列分小序列排序,排序后小序列间进行排序,组成大序列,循环
    
  • 基数排序

    不需要比较的排序。适合大数据量
    排序思想:根据数字的个位进行排序,再根据数字十位排序,百位排序,以此类推
    
  • 排序算法的时间复杂度,空间复杂度