数据结构(附代码) 整理与总结


栈 ————先进后出

栈的顺序实现

typedef int Position;
typedef int ElementType;
typedef struct SNode * PtrToSNode;
struct SNode{ //封装成结构体
    ElementType *Data; //data数组  下标从0--MaxSize-1
    Position Top; //top的位置 一开始初始化成-1
    int MaxSize; //容量
};
typedef PtrToSNode Stack;

Stack CreateStack(int Maxsize){ //创建
    Stack S = (Stack)malloc(sizeof(struct SNode)); //申请整个结构体的空间
    S->Data = (ElementType*)malloc(Maxsize*sizeof(ElementType)); //结构体内数组的空间
    S->Top = -1;
    S->MaxSize = Maxsize;
    return S;
}
bool IsFull(Stack S){  //满:下标
    return (S->Top==S->MaxSize-1);
}
bool Push(Stack S,ElementType X){
    if(IsFull(S)){
        printf("Full\n");
        return 0;
    }
    else{
        S->Data[++(S->Top)] = X;
        return 1;
    }
}
bool IsEmpty(Stack S){
    return (S->Top==-1);
}
ElementType Pop(Stack S){
    if(IsEmpty(S)){
        printf("null\n");
        return error;
    }
    else
        return S->Data[(S->Top)--];
}

一维数组实现双栈

两个栈分别从数组的两头开始向中间生长

栈的链式实现

typedef int ElementType;
typedef struct SNode *PtrToSNode;
struct SNode{ 
    ElementType Data;
    PtrToSNode Next;
};
typedef PtrToSNode Stack;

Stack CreateStack(){
    Stack S; //链表头指针
    S = (Stack)malloc(sizeof(SNode));
    S->Next = NULL;
    return S;
}
bool IsEmpty(Stack S){ 
    return (S->Next==NULL); 
}
bool Push(Stack S,ElementType X){ 
    PtrToSNode TmpCell;
    TmpCell = (PtrToSNode)malloc(sizeof(struct SNode));
    TmpCell->Data = X;
    TmpCell->Next = S->Next;
    S->Next = TmpCell;
    //s本身没有数,永远是指向的下一个是top元素,tmp插入s后面
    return 1;
}
ElementType Pop(Stack S){
    PtrToSNode FirstCell;
    ElementType TopElem;
    if(IsEmpty(S)){
        printf("NULL\n");
        return error;
    }
    else{
        FirstCell=S->Next; 
        //s指向的下一个,是top元素
        TopElem = FirstCell->Data;
        S->Next = FirstCell->Next;
        free(FirstCell);
        return TopElem;
    }
}

应用:后缀表达式


队列

队列的顺序实现

#define ERROR 0x3f3f3f3f
typedef int ElementType;
typedef int Position;
struct Node{
    ElementType *Data;
    Position Front,Rear;
    int Maxsize;
};
typedef Node * Queue;

Queue CreateQueue(int Maxsize){    		 //初始化
    Queue q = (Queue)malloc(sizeof(Node));
    q->Data = (ElementType*)malloc(sizeof(ElementType)*Maxsize);
    q->Maxsize = Maxsize;
    q->Front = q->Rear = 0; //不能是-1⚠️⚠️
    return q;
}
bool IsFull(Queue q){  		 //判断是否满
    return ((q->Rear+1)%q->Maxsize) == q->Front;
}
bool IsEmpty(Queue q){ 		 //判断是否为空
    return q->Front==q->Rear;
}
bool Pop(Queue q){  		 //从队首删除
    if(IsEmpty(q)) return 0;
    q->Front = (q->Front+1)%q->Maxsize;
    return 1;
}
bool Push(Queue q,ElementType x){		 //从队尾添加
    if(IsFull(q))
        return 0;
    q->Rear =(q->Rear+1)%q->Maxsize;
    q->Data[q->Rear] = x;
    return 1;
}
ElementType GetFront(Queue q){  	 //获得队首第一个
    if(IsEmpty(q)){
        puts("isempty");
        return ERROR;
    }
    q->Front = (q->Front+1)%q->Maxsize;
    return q->Data[q->Front];
}

void Print(Queue q){
    if(IsEmpty(q)) cout << "空的" << endl;
    else{
        for(Position i=q->Front+1; i<=q->Rear; i++)
            cout << q->Data[i] << " ";
        cout << endl;
    }
}

队列的链式实现

#define ERROR 0x3f3f3f3f
typedef int ElementType;
struct Node{         //单个元素
    ElementType Data;
    struct Node * next;
};
typedef struct Node * PtrToNode;
struct QNode{         //队列头尾指针
    PtrToNode Front, Rear;
};
typedef struct QNode * PtrToQNode;
typedef PtrToQNode Queue;

Queue CreateQueue(){         //初始化
    Queue q = (Queue)malloc(sizeof(QNode));
    q->Front = q->Rear = (PtrToNode)malloc(sizeof(struct Node)); //带头节点
    q->Front->next =  NULL;
    return q;
}
bool Isempty(Queue q){         //判断是否为空
    return q->Front==q->Rear;
}
void Push(Queue q,ElementType x){         //从队尾入队
    PtrToNode t = (PtrToNode)malloc(sizeof(struct Node));
    t->Data = x;
    t->next = NULL;
    q->Rear->next = t;
    q->Rear = t;
}

bool Pop(Queue q){         //从队首删除
    if(Isempty(q)) return 0;
    if(q->Front->next==q->Rear) q->Rear = q->Front;
        //删除到rear位置--只有一个元素
    q->Front->next = q->Front->next->next;
    return 1;
}
ElementType GetFront(Queue q){         //取出第一个元素
    if(Isempty(q))
        return ERROR;
    return q->Front->next->Data;
}

void Print(Queue q){
    if(q->Front->next==NULL) printf("空的\n");
    else{
        PtrToNode t = q->Front->next;
        while(t){
            cout << t->Data << " ";
            t = t->next;
        }
        cout << endl;
    }
}

二叉树 BT

⚠️二叉树的基本概念

完全二叉树

一棵深度为k,n个结点的二叉树,从上到下从左到右编号,如果编号和满二叉树相同:完全二叉树

即叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。需要注意的是,满二叉树肯定是完全二叉树,而完全二叉树不一定是满二叉树。
完全二叉树判定编辑

算法思路
判断一棵树是否是完全二叉树的思路 [3]
1>如果树为空,则直接返回错
2>如果树不为空:层序遍历二叉树
2.1>如果一个结点左右孩子都不为空,则pop该节点,将其左右孩子入队列;
2.1>如果遇到一个结点,左孩子为空,右孩子不为空,则该树一定不是完全二叉树;
2.2>如果遇到一个结点,左孩子不为空,右孩子为空;或者左右孩子都为空;则该节点之后的队列中的结点都为叶子节点;该树才是完全二叉树,否则就不是完全二叉树;

!!!注意if判断的顺序 flag和都不为空的要在都不为空的判断前面

bool checkisComplete(Bintree T){
    if(!T) return 0;
    queue<Bintree>q;
    q.push(T);
    Bintree t;
    bool flag = 0;
    while(!q.empty()){
        t = q.front(); q.pop();
        if(t->right && !t->left) //右有 左无
            return 0;
        else if(flag && (t->right || t->left))
            return 0;
        else if(!flag && !t->right) //两种情况 左右空或者右空 那么之后的都要是叶子
            flag = 1;
        else if(t->left && t->right){
            q.push(t->left);
            q.push(t->right);
        }
    }
    return 1;
}

满二叉树(完美二叉树)

所有的分支节点都存在左右子树并且所有叶子结点在同一层上
一个二叉树层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树


链式存储

typedef struct TNode *Position;
typedef Position BinTree;
typedef int ElementType;
struct TNode{
    ElementType Data;
    BinTree Left;
    BinTree Right;
};


创建

仅中序:不可

先序创建—递归
void preCreateBinTree(BinTree &T){
    cin >> ch;
    if(ch=='0') T = NULL;
    else{
        T = (BinTree)malloc(sizeof(TNode));
        T->Data = ch; //根
        preCreateBinTree(T->Left); //左
        preCreateBinTree(T->Right); //右
    }
}
层序创建—队列
BinTree CreateBinTree(){ //层序创建
    ElementType a;
    cin >> a;
    if(a==-1) return NULL;
    BinTree T = (BinTree)malloc(sizeof(TNode));
    T->Data = a; T->Right = T->Left = NULL;
    queue<BinTree>q;
    BinTree t;
    q.push(T);
    while(!q.empty()){
        t = q.front(); q.pop();
        cin >> a;
        if(a=='0') t->Left = NULL;
        else{
            t->Left = (BinTree)malloc(sizeof(TNode));
            t->Left->Data = a;
            t->Left->Left = t->Left->Right = NULL;
            q.push(t->Left);
        }
        
        cin >> a;
        if(a=='0') t->Right = NULL;
        else{
            t->Right = (BinTree)malloc(sizeof(TNode));
            t->Right->Data = a;
            t->Right->Left = t->Right->Right = NULL;
            q.push(t->Right);
        }
    }
    return T;
}

二叉树的遍历

参考博客:点击链接

前序遍历 根左右
递归
void Preorder(BinTree T){ //先序遍历
    if(T){
        cout << T->Data << " ";
        Preorder(T->Left);
        Preorder(T->Right);
    }
}
堆栈
void Preorder(BinTree T){
    BinTree t = T;
    stack<BinTree>st;
    while(t || !st.empty()){
        while(t){ //左子树全放入
            cout << t->Data << " "; //先输出根 每次往左 都输出
            st.push(t);
            t = t->Left;
        }
        t = st.top(); //取出来转右边
        st.pop();
        t = t->Right;
    }
}
中序遍历 左根右
递归
void Inorder(BinTree T){ //中序遍历
    if(T){
        Inorder(T->Left);
        cout << T->Data << " " ;
        Inorder(T->Right);
    }
}
堆栈
void Inorder(BinTree T){
    BinTree t = T;
    stack<BinTree>st;
    while(t || !st.empty()){
        while(t){ //左子树全放入
            st.push(t);
            t = t->Left;
        }
        t = st.top(); //取出来
        cout << t->Data << " "; //输出根
        st.pop();
        t = t->Right; //转右边
    }
}
后序遍历 左右根
递归
void Postorder(BinTree T){ //后续遍历
    if(T){
        Postorder(T->Left);
        Postorder(T->Right);
        cout << T->Data << " ";
    }
}
堆栈
void Postorder(BinTree T){
    BinTree cur = T;
    stack<BinTree>st;
    BinTree last=NULL,top;
    while(cur || !st.empty()){
        while(cur){ //左子树全放入
            st.push(cur);
            cur = cur->Left;
        }
        top = st.top(); //取出来
        if(!top->Right || top->Right == last){
            st.pop();
            cout << top->Data << " " ;
            last = top;
        }
        else
            cur = top->Right;
    }
}
层序遍历
void Levelorder(BinTree T){ //层序遍历
    if(!T) return ;
    queue<BinTree>q;
    BinTree t;
    q.push(T);
    while(!q.empty()){
        t = q.front(); q.pop();
        cout << t->Data << " " ;
        if(t->Left) q.push(t->Left);
        if(t->Right) q.push(t->Right);
    }
}
基础遍历方法的应用

只遍历叶子结点
前中后序都能实现 且输出顺序相同:即语句123交换顺序 输出叶子节点的顺序不变
因为前中后序走遍树的路径时一样的,只是输出各个节点顺序不同
所以途径叶子结点的顺序是一样的,输出顺序也是一样的

void Printleaves(BinTree T){    //只遍历叶子结点:前中后序都能实现 且输出顺序相同
    static int cnt = 0;
    if(T){
        if(!T->Left && !T->Right){
            cnt++;
            cout << T->Data;
        } //1
        Printleaves(T->Left); //2
        Printleaves(T->Right); //3
    }
    
}

线存储

int T[1005];
void Create(){
    int N,a,b,c;
    cin >> N;
    memset(T,-1,sizeof(T));
    T[1] = 1;
    while(N--){
        cin >> a >> b >> c;
        T[a*2] = b;
        T[a*2+1] = c;
    }
}

void Preorder(int *T,int index){
    if(T[index] != -1){
        cout << T[index];
        Preorder(T,index*2);
        Preorder(T,index*2+1);
    }
}
void Inorder(int *T,int index){
    if(T[index]!=-1){
        Inorder(T,index*2);
        cout << T[index];
        Inorder(T,index*2+1);
    }
}
void Postorder(int *T,int index){ //左右根
    if(T[index]!=-1){
        Postorder(T,index*2);
        Postorder(T,index*2+1);
        cout << T[index];
        
    }
} 

恢复二叉树

思路:递归
找到根位置
递归去创建子树
跳到需要的先序/中序序列起始位置

先序+中序

BinTree Create(char* pre,char* mid,int len){
    if(len<=0) return NULL;
    BinTree T = (BinTree)malloc(sizeof(struct TNode));
    T->data = pre[0];
    
    int i;
    for(i=0; i<len; i++) 
        if(pre[0]==mid[i]) break;
    //当前的根 是pre【0】在mid中找到一致的部分,然后对于这个根来说,
    

    T->left = Create(pre+1,mid,i);
    T->right = Create(pre+i+1,mid+i+1, len-1-i);
    
    return T;
}

后序+中序

BinTree Create(int post[],int mid[],int len){
    if(len<=0) return NULL;
    BinTree T = (BinTree)malloc(sizeof(struct TNode));
    T->data = post[len-1];
    int i;
    for(i=0; i<len; i++)
        if(post[len-1]==mid[i]) break;
    //左右根
    //左根右
    T->left = Create(post,mid,i);
    T->right = Create(post+i,mid+1+i,len-i-1);
    return T;
}

深度

递归实现!

注意不能是static h,因为每次都是从0开始走,系统自动创建新的h,然后走出来一个深度之后跟当前的这个比较,更新第一个h,
最后返回第一个h

int getheight(BinTree T){
    int h = 0;
    if(T)
        h = max(getheight(T->left),getheight(T->right))+1;
    return h;
}

镜像反转

递归遍历的模板➕change

void reverse(BinTree T){ //加不加引用都可以
    if(T){
        BinTree t = T->left;
        T->left = T->right;
        T->right = t;
        reverse(T->left);
        reverse(T->right);
    }
}

二叉搜索树BST

概念

二叉排序树 二叉查找树
非空左子树所有键值小于其根节点
非空右子树所有键值大于其根节点
左右子树本身也都是二叉搜索树

查找元素X

递归的形式

Position Find(BinTree BST,ElementType x){
    if(!BST) return NULL;
    if(x > BST->Data)
        return Find(BST->Right,x); //return 不要漏掉
    else if(x < BST->Data)
        return Find(BST->Left,x); //return 不要漏掉
    else
        return BST;
}

非递归:

Position Find(BinTree BST,ElementType x){
    while(BST){
        if(x>BST->Data)
            BST = BST->Right;
        else if(x<BST->Data)
            BST = BST->Left;
        else break;
    }
    return BST;
}

对于二叉搜索树进行查找的时间复杂度是由查找过程中的比较次数来衡量的,比较是从根结点到叶结点的路径进行的,取决于树的深度,最好情况是O(logN),所以最好的查找复杂度是O(logN)
但是 eg 单枝树,查找的时间复杂度就成了线性的O(N)

查找最小/大元素

Position FindMin(BinTree BST){
    if(!BST) return NULL;
    else if(!BST->Left) return BST;
    else return FindMin(BST->Left); //不要忘记return
}
Position FindMax(BinTree BST){
    if(!BST) return NULL;
    else if(!BST->Right) return BST;
    else return FindMax(BST->Right);
}

插入:成为叶子结点:可以用于创建

如果存在:插入失败
如果不存在 循环找 直到找到

Position Insert(BinTree BST,ElementType x){
    if(!BST){
        BST = (BinTree)malloc(sizeof(struct TNode));
        BST->Data = x;
        BST->Left = BST->Right = NULL;
    }
    else{
        if(x>BST->Data)
            BST->Right = Insert(BST->Right,x);//注意这里要 = 连起来
        else if(x<BST->Data)
            BST->Left = Insert(BST->Left,x);
    }
}

删除

叶子结点:直接删,其父节点的指针置空
非叶子结点 只有一个孩子:孩子连到父亲的父亲上
非叶子结点 有左右两颗子树 那么要补上这个位置 保持有序性:用右子树中最小的元素或者左子树中最大的 :递归找到的这个结点一定是叶子结点或者是只有一个孩子结点的

BinTree Delete(BinTree BST,ElementType X){
    BinTree t;
    if(!BST) printf("no"); //空树
    /*

	if(!Find(BST,X)){
        puts("Not Found");
        return BST;
    }
	*/
    else{
        if(X>BST->Data)
            BST->Right = Delete(BST->Right,X); //要等于 连起来
        else if(X<BST->Data)
            BST->Left = Delete(BST->Left,X);
        else{
            if(BST->Left && BST->Right){	//2个孩子节点
                t = FindMin(BST->Right); //右找最小
                //t = FindMax(BST->Left);
                BST->Data = t->Data; //填充 注意不是直接等于t 物理位置不变!
                BST->Right = Delete(BST->Right,BST->Data); //最小的删
            }
            else{ 				//0/1个子结点
                t = BST;
                if(BST->Right) //只有右孩子放上来
                    BST = BST->Right;
                else
                    BST = BST->Left;
                free(t);
            }
        }
    }
    return BST;
}

搜索树判断

bool IsBST ( BinTree T ){
    static BinTree B = NULL;
    if(T){
        if(!IsBST(T->Left)) return false; //B及时记录T,T往下走,保证:左都是越来越小的
        if(B && T->Data < B->Data )
            return false;
        B = T;
        //非空左右子树的所有键值大于其根结点的键值,右侧子树小的部分不能小于根 要记录一下根
        if(!IsBST(T->Right)) return false;
    }
    return true;
}

创建

for ( i=0; i<N; i++ ) {
        scanf("%d", &X);
        BST = Insert(BST, X);
    }

lCA

int Find(Tree T,int x){
    while(T){
        if(x>T->Key) T = T->Right;
        else if(x<T->Key) T = T->Left;
        else break;
    }
    return T==NULL?0:1;
}
int LCA( Tree T, int u, int v ){
    if(!T) return ERROR;
    if(!Find(T,u) || !Find(T,v)) return ERROR;
    if(T->Key>u && T->Key>v)
        return LCA(T->Left,u,v);
    if(T->Key<u && T->Key<v)
        return LCA(T->Right,u,v);
    return T->Key;
}

是否完全二叉搜索树

是否完全树+ 二叉搜索树


平衡二叉树

定义

平衡二叉树 AVL树
其插入删除查找操作均可在O(logN)下完成

定义:
(1)左右子树都是AVL
(2)根结点左右子树高度差不超过1 (0/1)

平衡因子
BF:balance factor
BF(T) = 左子树高度-右子树高度
AVL的BF只能从 -1 0 1取值

插入

插入新的节点时,有可能破坏树的平衡:四种 LL RR LR RL

因此:局部旋转

单旋

RR 向右倾斜型不平衡 —— 逆时针旋转——右单旋
LL 向左倾斜型不平衡 —— 顺时针旋转——左单旋

记忆
右单旋,以右为起点,往左转。解决向右倾斜的问题

理解:两侧深度差大于1就旋一下,使其相等
注意:三个节点的局部的旋转调整即可=

双旋

LR先左后右 ——左-右双旋 (先右单旋再左单旋)
为什么叫左右双旋emmm根据LR记住吧

RL

记忆
先左偏后右偏,右更靠近底层,所以先右单旋解决右偏,再往左单旋解决左偏


定义概念

存储实现

原理:其实是线性的
角标当成树的结点

#define MAXDATA 0x3f3f3f3f
#define ERROR -1

typedef struct HNode * Heap;
typedef int ElementType;
struct HNode{
    ElementType * Data;
    int Size; 
    int Capacity;
};
typedef Heap MaxHeap;
typedef Heap MinHeap;

创建/ 初始化

MaxHeap CreateHeap(int MaxSize){
    MaxHeap H = (MaxHeap)malloc(sizeof(struct HNode));
    H->Data = (ElementType*)malloc(sizeof(ElementType)*(MaxSize+1));
    H->Size = 0;
    H->Capacity = MaxSize;
    H->Data[0] = MAXDATA;
    return H;
}

满了/空的

bool IsFull(MaxHeap H){
    return H->Size==H->Capacity;
}
bool IsEmpty(MaxHeap H){
    return H->Size==0;
}

插入 从最后位置往上比较到该放的位置

插入一个新的,先放在最后的位置(并不是真的放了),再往上一个一个比较,上一层的小于新的,上一层的放到下面,直到上一层的大于新的,新的就插在这个位置。

不是从n/2开始一直检查到1,这样会做很多无用功,从最后位置往上比较到该放的位置 ,顺着走一条就行了

每次/2是因为跟根比较即可 左右子树不用比较因为没有大小顺序

bool Insert(MaxHeap H,ElementType X){
    if(IsFull(H))
        return 0;
    int i = ++H->Size;
    for( ;H->Data[i/2]<X; i/=2)
        H->Data[i] = H->Data[i/2];
    H->Data[i] = X;
    return 1;
}

删除

删除其实就是取出data[1],并且从下面找到一个最大值,填充上来。
堆的最后一个结点的位置需要操作
因为size-1了,最后一个节点先放到第一个的位置,再依次往下比较即可

ElementType Delete(MaxHeap H){
    if(IsEmpty(H))
        return ERROR;
    ElementType MaxItem = H->Data[1];
    ElementType X = H->Data[H->Size--];
    int Parent,Child;
    for(Parent = 1,Child; Parent*2<=H->Size; Parent=Child){
        Child = Parent*2;
        if((Child!=H->Size) && (H->Data[Child+1]>H->Data[Child]))
            Child++;
        if(X>=H->Data[Child])
            break;
        else
            H->Data[Parent] = H->Data[Child];
    }
    H->Data[Parent] = X;
    return MaxItem;
}

将已存在的数组转变为堆 不等于插入建堆

小顶堆改变比较的条件

void PercDown(MaxHeap H, int p){ //根结点往下筛一遍(三角形)
    ElementType X = H->Data[p];
    int Parent,Child;
    for(Parent=p; Parent*2<=H->Size; Parent=Child){
        Child = Parent*2;
        if((Child!=H->Size) && (H->Data[Child+1]>H->Data[Child]))
            Child++;
        
        if(X>=H->Data[Child]){
            break;
        }
        else
            H->Data[Parent] = H->Data[Child];
    }
        H->Data[Parent] = X;

}

void BuildHeap(MaxHeap H){
    for(int i=H->Size/2; i>0; i--) //每个根结点往下筛一遍
        PercDown(H,i);
}

删除插入都能写成上下过滤

#include <iostream>
#include <stdio.h>
#include <stdlib.h>

typedef int Elem;

struct heap{
    Elem *data;
    int capacity; 
    int size; //当前队列容量
};

using namespace std;

typedef struct heap* Heap; 
Heap initHeap(int max){ //创建
    Heap h;
     h = (Heap)malloc(sizeof(struct heap));
     if(!h)return NULL;
     h->data = (Elem*) malloc(sizeof(Elem)*(max +1));
     if(!h->data)return NULL;
     h->capacity = max;
     h->size = 0;
     return h;

}
void destroy(Heap h) 
{
    if(h->data)free(h->data);
    if(h)free(h);
}

void printHeap(Heap h){ //打印输出
    if(h){
        for(int i=1;i<=h->size;i++)
            if(i==1)printf("%d",h->data[i]);	//C语言有局限性
            else printf(" %d",h->data[i]);
        putchar('\n');
    }
}
int isEmpty(Heap h) 
{
    return h->size==0;
}

int isFull(Heap h){
    return h->capacity == h->size; //判断满
}

void percolateUp(int k,Heap h){ //上滤
    Elem x;
    x = h->data[k];
    int i;
    for( i=k;i>1 && x<h->data[i/2];i /= 2 )//最小堆 用x比较
    {
        h->data[i] = h->data[i/2];
    }
    h->data[i] = x ;
}
void percolateDown(int k,Heap h){  //下滤
    Elem x;
    x = h->data[k];
    int i,child;
    for(i=k;i*2<=h->size;i=child){
        child = i*2;
        if(child != h->size && h->data[child]> h->data[child +1])child++;
        if(x > h->data[child])h->data[i]= h->data[child];      //跟最小的比,如果不是最小,儿子上移
        else break;
    }
    h->data[i]=x;
}

// 插入堆
int insertHeap(Elem x,Heap h){
    if(isFull(h))return 0;
    h->data[++h->size]  = x; //在数组最后一个位置插入
    percolateUp(h->size, h); //上滤
    return 1;
}
//删除堆元素
int removeHeap(Elem *px,Heap h){
    if(isEmpty(h))return 0;
    *px = h->data[1]; //取堆顶元素
    h->data[1] = h->data[h->size--];
    percolateDown(1,h); //下滤
    return 1;
}
Heap buildHeap(Elem *a,int size,int max){
    Heap h= initHeap(max);
    if(!h)return NULL;
    h->size = size;
    for(int i=1;i<=size;i++)
        h->data[i] = a[i-1];
    for(int i=size/2;i>0;i--){
        percolateDown(i,h); //从有子节点的开始往上走 每个父节点都下滤一次调整
    }
    return h;
}

int main()
{
    int N,K,M;
    cin >>N>>K;
    Heap h;
    h = initHeap(N);
    int flag,tem;
    for(int i=0;i<K;i++){
        cin>>flag;
        if(1==flag){
            cin >> tem;
            insertHeap(tem,h);

        }
        else if(-1 ==flag)
        {
            removeHeap(&tem,h);
        }
        printHeap(h);
    }
    cin >>M;
    int a[M];
    for(int i=0;i<M;i++)cin>>a[i];
    Heap h2 = buildHeap(a,M,1000);
    printHeap(h2);

    return 0;
}



哈夫曼树

一棵树的路径长度是指从树根到其余各结点的路径长度之和
一个节点的带权路径长度是指从根结点到该节点之间的路径长度与该节点上的所带权值的乘积


带权路径长度 WPL
一棵树的带权路径长度是每个叶结点的带权路径长度之和

定义:

给定n个权值,构造具有n个叶子的二叉树,其中带权路径长度最小的的树就叫做哈夫曼树,也称为最优二叉树

注意
给定n个权值是叶子结点的值,只有叶子结点的权值*深度 需要计算

typedef struct HTNode * HuffmanTree;
struct HTNode{
    int Weight; //权值
    HuffmanTree Left;
    HuffmanTree Right;
};

构造

目的:权值越大的叶子结点越靠近根结点
思路:贪心算法
过程

  1. 给定n个权值,每一个独立成为只有一个叶子结点的二叉树(不是根结点),总共n个T,集合F
  2. 选择最小的两个,最为左右子树合并,根结点为左根+右根
  3. F删除构造过的两个,并且将构造后的结果放入
  4. 重复直到F只有一个

在这里插入图片描述

核心

HuffmanTree Huffman(MinHeap H){
    //小顶堆:权值小的放上面 元素类型是HuffmanTree
    BuildHeap(H); //将H->data按照权值调整成小顶堆 每次拿最小的两个
    HuffmanTree T;
    
    int N = H->Size;
    for(int i=0; i<N; i++){
        T = (HuffmanTree)malloc(sizeof(struct HTNode));
        T->Left = Delete(H);
        T->Right = Delete(H);
        T->Weight = T->Left->Weight + T->Right->Weight;
        Insert(H,T); //将新的T插入到最小堆
    }
    return Delete(H);
    
}

完整


typedef struct HTNode * HuffmanTree;
struct HTNode{
    int Weight; //权值
    HuffmanTree Left;
    HuffmanTree Right;
};


#define MAXDATA 0x3f3f3f3f
#define MINDATA -0x3f3f3f3f
#define ERROR -1
typedef struct HNode * Heap;
typedef HuffmanTree ElementType;
struct HNode{
    ElementType * Data;
    int Size;
    int Capacity;
};

typedef Heap MaxHeap;
typedef Heap MinHeap;


void PercDown(MinHeap H, int p){ //根结点往下筛一遍(三角形)
    ElementType X = H->Data[p];
    for(int Parent=p,Child; Parent*2<=H->Size; Parent=Child){
        Child = Parent*2;
        if((Child!=H->Size) && (H->Data[Child+1]<H->Data[Child]))
            Child++;
        
        if(X<=H->Data[Child]){
            H->Data[Parent] = X;
            break;
        }
        else
            H->Data[Parent] = H->Data[Child];
    }
}

void BuildHeap(MinHeap H){
    for(int i=H->Size/2; i>0; i--) //每个根结点往下筛一遍
        PercDown(H,i);
}
bool IsFull(MaxHeap H){
    return H->Size==H->Capacity;
}
bool IsEmpty(MaxHeap H){
    return H->Size==0;
}
bool Insert(MinHeap H,ElementType X){
    if(IsFull(H))
        return 0;
    int i = ++H->Size;
    for( ;H->Data[i/2]>X; i/=2)
        H->Data[i] = H->Data[i/2];
    H->Data[i] = X;
    return 1;
}
ElementType Delete(MinHeap H){
    if(IsEmpty(H))
        return NULL;
    ElementType MinItem = H->Data[1];
    ElementType X = H->Data[H->Size--];
    for(int Parent = 1,Child; Parent*2<=H->Size; Parent=Child){
        Child = Parent*2;
        if((Child!=H->Size) && (H->Data[Child+1]<H->Data[Child]))
            Child++;
        if(X<=H->Data[Child]){
            H->Data[Parent] = X;
            break;
        }
        else
            H->Data[Parent] = H->Data[Child];
    }
    return MinItem;
}
HuffmanTree Huffman(MinHeap H){
    //小顶堆:权值小的放上面 元素类型是HuffmanTree
    BuildHeap(H); //将H->data按照权值调整成小顶堆 每次拿最小的两个
    HuffmanTree T;
    
    int N = H->Size;
    for(int i=0; i<N; i++){
        T = (HuffmanTree)malloc(sizeof(struct HTNode));
        T->Left = Delete(H);
        T->Right = Delete(H);
        T->Weight = T->Left->Weight + T->Right->Weight;
        Insert(H,T); //将新的T插入到最小堆
    }
    return Delete(H);
    
}


哈夫曼编码


集合 并查集

集合是一种常用的数据表示方法,集合运算包括交并补差,判断元素是否属于等
为了有效的实现集合的操作,可以用树结构表示集合
在这里插入图片描述
注意:这里子结点指向父结点有利于判断元素属于哪个集合,也便于集合的归并运算:运算关注集合中的元素而不是集合本身
所以,集合名结构其实是没有必要存在的:直接用树的根结点的编号来代表一个集合
N个元素,标号0-N-1,顺序存放到数组里,在这里插入图片描述
数组第8个元素存放3,表示其父结点是3 pre[x8] = 3在这里插入图片描述

英文:Disjoint Set,即“不相交集合”
将编号分别为1…N的N个对象划分为不相交集合,
在每个集合中,选择其中某个元素代表所在集合。

常见两种操作:
合并两个集合
查找某元素属于哪个集合

定义


#define MAXN 1000 //集合最大元素的个数
typedef int ElementType;
typedef int SetName; //默认用根结点的下标来作为集合的名称
typedef ElementType SetType[MAXN];

合并查找两个操作的实现

简单暴力

在这里插入图片描述

按照深度不同合并

深度小的树合并到深度大的树
在这里插入图片描述

路径压缩

在这里插入图片描述

在这里插入图片描述


散列查找

之前学过的查找
顺序查找
二分查找
二叉搜索树

基本概念

  • 符号表 散列表 哈希表 Table

  • 装填因子
    a = n/m
    装填因子 = 填入表中的元素个数 / 散列表的空间大小
    ——空间大小 = 填入表中的元素个数/装填因子

  • 散列
    散列是一种重要的查找方法
    根据数据对象的关键字为自变量,通过一个确定的函数关系h,计算出响应的hkey,这个值作为数据对象的存储地址

散列函数构造方法

数字关键词

直接定址法除留余数法数字分析法
找到一个线性函数作为散列地址散列表长TableSize = n/a ,p为≦TableSize的某个最大素数如果关键词位数太多eg手机号,那么只留下四位
h(key) = a*key+bh(key) = key mod ph(key) = atoi(key+7)

|TableSize|8|16|32|64|128|256|512|1024|
|–|–|–|–|–|–|–|–|–|–|–|–|–|–|–|
|P|7|13|31|61|127|251|503|1019

地址p到TableSize-1是不能通过映射函数直接映射到的

字符串关键词

ASCII码加和法前三个字符移位法移位法
27进制:26字母+空格 ; 注意不够三位\0也计算n个字符都计算 每位字符占5位 这样做✖️法运算32(2^5 =32)的时候就可以左移了
每个字符ASCII加和( key0 + key1 * 27 + key2 * 27^2)mod TableSize(keyn-1*32^ 0 + keyn-2 * 32^1 + keyn-3 *32 ^ 2 +……)mod TableSize
int Hash(const char *Key, int TableSize){
    unsigned int H = 0;
    while(*Key!='\0')
        H = (H<<5) + *Key++;
    return H%TableSize;
}

处理冲突的办法

1⃣️ 开放地址法

  1. 线性探测法
    1 2 3 4 5……
  2. 平方探测法
    1方 - 1方 2方 -2方……
  3. 双散列探测法
  4. 再散列法

2⃣️ 分离链接法
所有关键词为同义词的数据对象通过结点链接存储在同一个单链表内
散列函数得到的散列地址做为指针指向一个链


树:分枝层次关系
图:邻接关系
在这里插入图片描述

图的定义和术语

在这里插入图片描述

相关术语
无向图
有向图
简单图
邻接点
路径
简单路径
回路
无向完全图
有向完全图
入度出度
稠密图
稀疏图
网图
子图
连通图
强连通图
生成树
生成森林

在这里插入图片描述

存储结构

|||
|–|–|–|
|链表
|数组|一维
||二维

操作

最小生成树Kruskal并查集 选min判断是否有回路
Prim当前树不断加入最近的边
单源最短路径Dijkstradist path 每次选离起点距离最近的 更新
每一对顶点之间的最短路径两次Dijkstra
Floyed水平和垂直的投影元素之和
搜索BFS
DFS

最小生成树


排序

链接

请添加图片描述

除了基数其余都是基于比较的函数

在这里插入图片描述

排序
选择排序简单选择排序选择最小的交换
堆排序选择max交换
插入排序简单插入未排序的逐一插入
希尔排序分组插入
交换排序冒泡交换相邻的
快速排序交换low high
归并排序
基数排序
基数

选择

直接选择

思想:
在未排序的序列中选择最小的和首位元素交换
接下来在未排序的序列中选择最小的和序列的第二位元素交换

void SimpleSelectionSort(int A[],int n){
    int min;
    for(int i=0; i<n-1; i++){ //每次待交换的位置 从0到n-2
        min = i;
        for(int j=i+1; j<n; j++) //用min一起比较就从i+1开始 如果不用就从i开始
            if(A[j]<A[min]) min = j;
        swap(A[i],A[min]);
    }
}

最好最坏都是一样的,需要走一遍才知道
所以任何情况都是
n + n-1 + n-2 …… = (1+n)*n/2
时间复杂度是O(N^2)
不稳定
eg 3 3 1


1⃣️思想:
利用堆这种数据结构
堆:特殊的二叉树 每个节点的值都小于/大于其父节点
由于堆是完全二叉树:堆排序用数组实现

2⃣️简单实现:空间复杂度O(N)
利用最大堆输出堆顶元素,将剩余的其余元素重新生成最大堆 继续输出
这样需要一个辅助数组

3⃣️高级实现:空间复杂度O(1)
生成最大堆
把堆顶元素与最后的元素交换位置
剩余元素重新生成最大堆:自上而下过滤一次(注意个数-1,排除已经排好序的元素)
PercDown(A, 0, i);
A数组:每次过滤的范围:从0–i
I:每次放的位置:从n—1

4⃣️因为堆没有规定相同的元素应该放在左子树还是右子树,所以堆排序是不稳定的。

5⃣️两种方法时间复杂度同
总共n个元素 每次需要取出第一个然后从上往下筛一遍树深
O(NLogN)

void PercDown(int A[],int p,int n){ //总共n个元素:为了确定下标 A[p]为根开始调整
    int parent,child;
    int x;
    x = A[p]; //根
    
    for(parent=p; parent*2+1<n; parent=child){ //下标从0开始
        child = parent*2+1;
        if(child!=n-1 && A[child+1]>A[child])
            child++;
        if(x>=A[child])
            break;
        else
            A[parent]=A[child];
    }
    A[parent]=x;
    
}
void HeapSort(int A[],int N){
    for(int i=N/2; i>=0; i--) //调整成最大堆
        PercDown(A,i,N);
    for(int i=N-1; i>0; i--){
        swap(A[0],A[i]);
        PercDown(A,0,i);
    }
}

java

   public void percolateDown(int k, int[] arr,int end) {
        int x = arr[k];
        int parent, child;
        for (parent = k; parent * 2 <= end; parent = child){
            child = parent*2;
            if(child!=end && arr[child+1]>arr[child]){
                child++;
            }
            if(x>arr[child]){
                break;
            }
            else{
                arr[parent] = arr[child];
            }
        }
        arr[parent] = x;
    }

    public void heapSort(int[] arr) {
        int len = arr.length;
        for(int i=arr.length/2; i>0; i--){
            percolateDown(i,arr,len);
        }
        int pos = arr.length;
        int temp;
        for(int i=0; i<arr.length; i++){
            temp = arr[pos];
            arr[pos] = arr[0];
            arr[0] = temp;
            percolateDown(0,arr,--len);
        }
    }

插入

简单插入

1⃣️思想:已排好序部分和未排好序部分,依次比较 swap
初始状态已排序只有一个元素,未排序N-1
将未排序的元素逐一插入到已排序的 总共插N-1次
具体实现:对应第K个元素,之前的K-1个默认排好序,
将其与K-1比较,swap
接着与K-2比较
直到不swap或者已经是第一个了
2⃣️实现

void InsertSort(int A[],int N){
    for(int p=1; p<N; p++){
        int t = A[p]; //未排序的第一个
        int i;
        for(i=p; i>0&&A[i-1]>t; i--)
            A[i] = A[i-1];
        A[i] = t;
    }
}

3⃣️稳定 :相同的元素 不会swap:相对位置不变
最好:每次走到一个数跟前面的比不用换就加入到已排好序部分 ON
最差:每次都要从未排好序的第一个走到已排好序的第一个 ON2
平均:n2
不需要递归 不需要辅助空间

希尔排序

1⃣️思想:
待排序的按照一定间隔(分成几组)分成若干序列 分别进行插入排序
最后间隔变成1
2⃣️实现

3⃣️
希尔排序会多次进行插入排序,一次插入排序是稳定的,但是因为希尔排序每次插入排序选择的步长不一样,导致希尔排序不稳定。

不需要递归 不需要辅助空间

时间复杂度跟其步长选择有关 目前没有最优 但是肯定小于N2
最差的:每一次的分组都要全部交换
最好:假如总共需要分组k次,每次都是不用交换 走一遍n即可,所以最好的是KN—N

 public void shellSort(int[] arr) {
        int len = arr.length;
        int gap = len >> 1;
        int i,j,temp;
        while (gap != 0) {
            for ( i = gap; i < len; i++) {
                temp = arr[i];
                //j大于i  j放到后面
                //temp是最小的 每次temp移到前面
                //当gap=1时就可以有序了
                for ( j = i - gap; j >= 0 && arr[j] > temp; j -= gap) {
                    arr[j+gap] = arr[j];
                }
                //j跳出时 小于0 加上gap才是当前不长的分组的第一位
                arr[j+gap] = temp;
            }
            gap >>= 1;
        }

    }

交换


冒泡

1⃣️思想:
i与i++判断是否swap , i++
每次最大的沉底
2⃣️
最坏:N+N-1+…… =(1+N)N/2 = N2
最好:flag标记
一次过后 都不用动:N

 public void bubbleSort(int[] arr) {
        int len = arr.length, temp;
        boolean flag;
        for (int i = 1; i < len; i++) { //n-1轮 从1开始表示次数 而不是数组下标
            flag = false;
            for (int j = 0; j < len - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    flag = true;
                    temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
            if(!flag){
                break;
            }
        }
    }

快速

1⃣️思想:
每次找到基准的正确位置
2⃣️实现:递归

java

 public void quickSort(int[] arr, int begin, int end) {
        if(begin>end){
            return;
        }
        int left = begin;
        int right = end;
        int temp = arr[begin];

        //没找到合适的 temp的位置
        while (left != right) {
            while (arr[left] <= temp && left < right) {
                left++;
            }
            while (arr[right] >= temp && left < right) {
                right--;
            }
            if (left < right) {
                int t = arr[left];
                arr[left] = arr[right];
                arr[right] = t;
                left++;
                right--;
            }
        }
        arr[begin] = arr[left];
        arr[left] = temp;
        quickSort(arr,begin,left-1);
        quickSort(arr,right+1,end);
    }
    public void quickSort(int[] arr, int begin, int end) {
        if(begin>end){
            return;
        }
        int left = begin;
        int right = end;
        int temp = arr[begin];

        //没找到合适的 temp的位置
        while (left != right) {
            while (arr[right] >= temp && left < right) {
                right--;
            }
            while (arr[left] <= temp && left < right) {
                left++;
            }
            if (left < right) {
                int t = arr[left];
                arr[left] = arr[right];
                arr[right] = t;
//                left++;
//                right--;
            }
        }
        arr[begin] = arr[left];
        arr[left] = temp;
        quickSort(arr,begin,left-1);
        quickSort(arr,right+1,end);
    }

注意顺序

3⃣️
最坏时间复杂度:每次都没有能够折半 N^2
最好和平均:可以折半或者类似折半 NlogN
快速排序的时间复杂度和是否有序无关,是看每次基准能否把递归的子列一分为二:递归的深度是LogN
所以一开始有序也不是NlogN
辅助空间
由于递归 也就是需要深度这么多LogN


归并

1⃣️思想:建立在归并操作上
归并操作:两个已经排好序的子序列合并成一个有序序列的过程
归并:长为N的序列看成N个长为1的子序列 接下来两两归并 成长度为2的
接下来继续2和2的归并

2⃣️
递归实现

3⃣️
每次左右一半要求有序 深度LogN
NlogN
每次都要遍历两个子序列 :不管好坏都一样
额外空间存放合并后的结果 O(N)
稳定

java


 public int[] merge(int[] left, int[] right) {
        int[] result = new int[left.length + right.length];
        for (int index = 0, i = 0, j = 0; index < result.length; index++) {
            if (i >= left.length) { //i没了
                result[index] = right[j++]; //用j
            } else if (j >= right.length) {
                result[index] = left[i++];
            } else if (left[i] < right[j]) {
                result[index] = left[i++];
            } else {
                result[index] = right[j++];
            }
        }
        return result;
    }

    public int[] mergeSort(int[] arr) {
        if (arr.length < 2) {
            return arr;
        }
        int mid = arr.length / 2;
        int[] left = Arrays.copyOfRange(arr, 0, mid);
        int[] right = Arrays.copyOfRange(arr, mid, arr.length);
        left = mergeSort(left);
        right = mergeSort(right);
        return merge(left, right);
    }


基数

N个待排序的 R个桶 D待排序的数按照基数分解的位数(需要几趟)
D(N+R)
好坏平均都一样
额外:链表实现 每个基数后面连着待排序的部分:N+R的
稳定

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
16进制10进制.txt 32.txt asm.txt Crctable.txt C标志符命名源程序.txt erre.txt erre2.txt ff.txt for循环的.txt list.log N皇后问题回溯算法.txt ping.txt re.txt source.txt winsock2.txt ww.txt 万年历.txt 万年历的算法 .txt 乘方函数桃子猴.txt 乘法矩阵.txt 二分查找1.txt 二分查找2.txt 二叉排序树.txt 二叉树.txt 二叉树实例.txt 二进制数.txt 二进制数2.txt 余弦曲线.txt 余弦直线.txt 傻瓜递归.txt 冒泡排序.txt 冒泡法改进.txt 动态计算网络最长最短路线.txt 十五人排序.txt 单循环链表.txt 单词倒转.txt 单链表.txt 单链表1.txt 单链表2.txt 单链表倒序.txt 单链表的处理全集.txt 双链表正排序.txt 反出字符.txt 叠代整除.txt 各种排序法.txt 哈夫曼算法.txt 哈慢树.txt 四分砝码.txt 四塔1.txt 四塔2.txt 回文.txt 图.txt 圆周率.txt 多位阶乘.txt 多位阶乘2.txt 大加数.txt 大小倍约.txt 大整数.txt 字符串查找.txt 字符编辑.txt 字符编辑技术(插入和删除) .txt 完数.txt 定长串.txt 实例1.txt 实例2.txt 实例3.txt 小写数字转换成大写数字1.txt 小写数字转换成大写数字2.txt 小写数字转换成大写数字3.txt 小字库DIY-.txt 小字库DIY.txt 小孩分糖果.txt 小明买书.txt 小白鼠钻迷宫.txt 带头结点双链循环线性表.txt 平方根.txt 建树和遍历.txt 建立链表1.txt 扫描码.txt 挽救软盘.txt 换位递归.txt 排序法.txt 推箱子.txt 数字移动.txt 数据结构.txt 数据结构2.txt 数据结构3.txt 数组完全单元.txt 数组操作.txt 数组递归退出.txt 数组递归退出2.txt 文件加密.txt 文件复制.txt 文件连接.txt 无向图.txt 时间陷阱.txt 杨辉三角形.txt 栈单元加.txt 栈操作.txt 桃子猴.txt 桶排序.txt 检出错误.txt 检测鼠标.txt 汉字字模.txt 汉诺塔.txt 汉诺塔2.txt 灯塔问题.txt 猴子和桃.txt 百鸡百钱.txt 矩阵乘法动态规划.txt 矩阵转换.txt 硬币分法.txt 神经元模型.txt 穷举搜索法.txt 符号图形.txt 简单数据库.txt 简单计算器.txt 简单逆阵.txt 线性顺序存储结构.txt 线索化二叉树.txt 绘制圆.txt 编随机数.txt 网络最短路径Dijkstra算法.txt 自我复制.txt 节点.txt 苹果分法.txt 螺旋数组1.txt 螺旋数组2.txt 试题.txt 诺汉塔画图版.txt 读写文本文件.txt 货郎担分枝限界图形演示.txt 货郎担限界算法.txt 质因子.txt 输出自已.txt 迷宫.txt 迷宫问题.txt 逆波兰计算器.txt 逆矩阵.txt 逆阵.txt 递堆法.txt 递归桃猴.txt 递归车厢.txt 递推.txt 逻辑移动.txt 链串.txt 链栈.txt 链表十五人排序.txt 链表(递归).txt 链队列.txt 队列.txt 阶乘递归.txt 阿姆斯特朗数.txt 非递归.txt 顺序栈.txt 顺序表.txt 顺序队列.txt 骑士遍历1.txt 骑士遍历2.txt 骑士遍历回逆.txt 黑白.txt
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值