基础数据结构与算法总结3(附有完整代码,适合小白)

目录

前言

13,哈希表

14,二叉搜索树 

14.1,二叉搜索树完整代码如下

15,平衡二叉树

15.1,平衡二叉树完整代码如下:

16,排序篇

16.1,插入排序(稳定)

16.2,插入排序完整代码

16.3,冒泡排序(稳定)

16.4,冒泡排序完整代码

16.5,快速排序(不稳定)

16.6,快速排序完整代码

16.7,合并排序(稳定)

16.8,合并排序完整代码

16.9,选择排序(不稳定) 

16.10,选择排序完整代码如下:

16.11,堆排序(不稳定)

16.12,堆排序完整代码如下:

16.13,桶排序(稳定)

16.14,基数排序(稳定) 


前言


以下算法和数据结构代码都可以在本人的GitHub仓库中找到,欢迎大家来下载,可以的话,请给博主的GitHub来一个star,GitHub链接如下https://github.com/hifuda/algorithm-and-data-structure,但是我还是把完整的代码也放在了我的本篇博客上,为了照顾不玩GitHub的朋友。本篇博客是跟着b站视频来学习的,链接如下https://www.bilibili.com/video/BV1LV411z7Bq?p=33&spm_id_from=pageDriver&vd_source=d5a99b3b2961a4345794b35726979478,由于篇幅有限,我打算分篇更新。感谢大家的观看,求个赞!
 

13,哈希表

1,哈希表

在理想状况下,每个关键字对应一个地址,时间复杂度为O(1)

但是大部分是容易发生冲突的,如下图:

哈希表的两个关键要素有,散列函数和解决冲突办法。

散列函数的设计原则,简单均匀

2,常用的散列函数

1,直接定址法

缺点:空间浪费大,需要事先知道关键字

2,除留余数法

3,随机数法

4,数字分析法:

也需要事先知道关键字集合

5,平方取中法:

6,折叠法:

7,基数转换法:

8,全域散列法

用的很少,因为我们还是趋向于在特定的环境使用特定的算法

3,处理冲突的方法

1,开放定址法:

线性探测:di是一个一个的增大,比如说1,2,3,4,5

二次探测:如下

随机探测:di是一个随机数

再散列法:将原来的hash值再次使用原来的散列函数哈希一次

线性探测如下:

只要有空间,就一定可以探测到位置,一发生冲突就往后放,然后再写上比较次数

1,线性探测(容易产生堆积现象)

2,二次探测

缺点:可能会出现明明有空间,但是却无法他测到的现象,虽然效率高

开放链地址法不可以随便删除表中的元素,删除后会截断后续元素的探测,要删除的话,需要先打标记

2,链地址法(就像邻接表)

3,建立公共缓冲区

14,二叉搜索树 

1,二叉搜索树

算法实现:

要注意算法停止的条件,要么就是找到了,要么就是找完了还没有找到

算法复杂度:最好的情况为O(logn),最坏的情况下会退化到O(n)

我们在输入数据的时候,首先给输入的数据调平衡

二叉树的插入:

插入之前先,对数进行查找,看是否已经存在这个元素节点,查找的时间复杂度为O(logn),插入的时间为

0

但是,如果我们查找的元素在树中已经存在,那么该怎么办呢?处理的方法有两种,1,我们发现相同之后什么也不做,直接返回。2,我们为每一个元素节点添加一个num域,来记录这种元素节点在树中出现在的次数。

算法实现:

二叉树创建:

算法分析:

输入算法时间复杂度O(nlogn)

二叉树的删除:

三种情况:

1,被删除的节点只有左子树

让被删除节点的左子树直接替代父亲的位置,子承父业

2,被删除的节点只有右子树

让被删除节点的右子树直接替代父亲的位置,子承父业

3,被删除的节点左右子树都有

有两种方法,一种是是使用这个节点的直接前驱去覆盖这个节点,还有一种就是使用这个节点的直接后继去覆盖这个节点,然后删除这个直接后继。

打比方:下面有一列数,我要删除这列数中的10

1,5,10,15,20.那么5就是10的直接前驱,15就是10的直接后继。

那么我们又如何在树中找到一个数的直接前驱和直接后继呢?

如下图:

p指针指向的节点是要被删除的节点。

删除过程:

特殊情况:

删除20

删除操作的时间复杂度为O(logn)

14.1,二叉搜索树完整代码如下

#include<iostream>
using namespace std;

typedef int ElemType;

typedef struct BSTNode{
    ElemType data;
    struct BSTNode *lchild,*rchild;
}BSTNode, *BSTree; 

void CreateBST(BSTree &T);                    //创建二叉搜索树 
void InOrderTraverse(BSTree &T);            //输出二叉搜索树的中序遍历的序列 
BSTree SearchBST(BSTree T,ElemType key);     //查找元素 
void DeleteBST(BSTree &T,ElemType key);        //删除元素 
void InsertBST(BSTree &T,ElemType e);        //插入元素 

int main()
{
    BSTree T;
    cout<<"请输入一些整型数,-1结束"<<endl;
    CreateBST(T);
    cout<<"当前有序二叉树中序遍历结果为"<<endl;
    InOrderTraverse(T);
    cout<<endl;
    ElemType key;//待查找或待删除内容
    cout<<"请输入待查找关键字"<<endl;
    cin>>key;
    BSTree result=SearchBST(T,key);
    if(result)
        cout<<"找到"<<key<<endl;
    else
        cout<<"未找到"<<key<<endl;
    cout<<"请输入待删除关键字"<<endl;
    cin>>key;
    DeleteBST(T,key);
    cout<<"当前有序二叉树中序遍历结果为"<<endl;
    InOrderTraverse(T);
    return 0;
}
//删除元素
void DeleteBST(BSTree &T,ElemType key){
    //从二叉排序树T中删除关键字等于key的结点
    BSTree p=T;BSTree f=NULL; //p指向被删除元素,f指向被删除元素的的父亲节点 
    BSTree q;//指向直接前驱的父亲节点 
    BSTree s;//指向被删除元素的直接前驱 
    if(!T) return; //树为空则返回
    while(p)//查找
    {
        if(p->data==key) break;  //找到关键字等于key的结点p,结束循环
        f=p;                //f为p的双亲
        if (p->data>key)
            p=p->lchild; //在p的左子树中继续查找
        else
            p=p->rchild; //在p的右子树中继续查找
    }
    if(!p) return; //找不到被删结点则返回
    if(p->lchild && p->rchild){//如果被删除元素既有左子树也有右子树 
        q = p;
        s = q->lchild;
        while(s){
            q = s;
            s = s->rchild;
        } 
        p->data=s->data;  //s的值赋值给被删结点p,然后删除s结点
        if(q!=p)
            q->rchild=s->lchild; //重接q的右子树
        else
            q->lchild=s->lchild; //重接q的左子树
        delete s;
    }else{//如果被删除元素只有左子树没有右子树 或者只有右没有左,但是处理方式一样子承父业 
        if(!p->lchild){//如果没有左子树,就挂接右子树 
            q = p;
            p = p->rchild;
        }else if(!p->rchild){//如果没有右子树,就挂接左子树 
            q = p;
            p = p->lchild;
        }
        if(!f){
            T = p;
        }else if(q == f->lchild){
            f->lchild = p;
        }else{
            f->rchild = p;
        }
        delete q;
    }
} 
//查找元素 
BSTree SearchBST(BSTree T,ElemType key){
    BSTree p,q; //p指向q节点的父亲
    q = T;        //让p指向根节点
    while(q){    //当q等于空的时候还没有找到,那么就说明不存在此元素 
        if(q->data == key){//直到相等才返回 
            return q;
        }else if(q->data > key){ //如果q节点的data大于被查找元素,那么就说明被查找元素在左子树 
            p = q;
            q  = q->lchild;
        }else{//反之就在右子树 
            p = q;
            q = q->rchild;
        }     
    } 
    return NULL;//循环结束还没有找到就直接返回NULL 
}
//中序遍历二叉搜索树 
void InOrderTraverse(BSTree &T){
    if(T){
        InOrderTraverse(T->lchild);
        cout << T->data << "\t";
        InOrderTraverse(T->rchild);
    } 
}
//插入节点 
void InsertBST(BSTree &T,ElemType e){
    BSTree s;
    if(!T){
        s = new BSTNode;
        s->data = e;
        s->lchild = s->rchild = NULL;
        T = s;
    }else if(T->data > e){
        InsertBST(T->lchild,e); //如果输入的元素小于根节点,那么就让输入的节点成为左子树 
    }else{
        InsertBST(T->rchild,e); //如果输入的元素大于根节点,那么就让输入的节点成为右子树 
    } 
}

//创建二叉搜索树 
void CreateBST(BSTree &T){
    int d;
    cin >> d;
    while(d != -1){ //当输入的元素不等于-1时,执行循环 
        InsertBST(T,d);
        cin >> d; 
    }
}
 

运行示例:

其中删除元素节点的算法较难理解,详细的删除元素节点的算法解析

1,首先我们要查找要被删除的节点,然后使用p指针指向这个被删除的节点,而f指针指向被删除节点的父亲节点。

while(p)//查找
    {
        if(p->data==key) break;  //找到关键字等于key的结点p,结束循环
        f=p;                //f为p的双亲
        if (p->data>key)
            p=p->lchild; //在p的左子树中继续查找
        else
            p=p->rchild; //在p的右子树中继续查找
    }

2,找到之后(没找到就直接返回),就看看这个被删除的节点是否有左子树,或者右子树,又或者二者都有。

2.1,如果二者都有,那么我们选择使用直接前驱来覆盖的方式,去删除节点,如下代码中的while语句中的代码就是寻找直接前驱的代码,

if(p->lchild && p->rchild){//如果被删除元素既有左子树也有右子树 
        q = p;
        s = q->lchild;
        while(s){
            q = s;
            s = s->rchild;
        } 
        p->data=s->data;  //s的值赋值给被删结点p,然后删除s结点
        if(q!=p)
            q->rchild=s->lchild; //重接q的右子树
        else
            q->lchild=s->lchild; //重接q的左子树
        delete s;
    }

2.2,如果是只有左子树或者右子树,那么比较简单,直接让被删除的节点的左子树或者右子树来代替被删除节点的位置。

else{//如果被删除元素只有左子树没有右子树 或者只有右没有左,但是处理方式一样子承父业 
        if(!p->lchild){//如果没有左子树,就挂接右子树 
            q = p;
            p = p->rchild;//这个地方只是对被删除的节点的覆盖
        }else if(!p->rchild){//如果没有右子树,就挂接左子树 
            q = p;
            p = p->lchild;//这个地方只是对被删除的节点的覆盖
        }
        if(!f){//判断被删除的节点是否是根节点
            T = p;
        }else if(q == f->lchild){
            f->lchild = p;//这个地方才是真正的挂接
        }else{
            f->rchild = p;//这个地方才是真正的挂接
        }
        delete q;
    }

15,平衡二叉树

适度平衡就好,因为花在调平衡的操作上也需要时间

AVL

平衡因子=左子树的深度-右子树的深度

调平衡的类型:

LL型

RR型:

LR型:

RL型:

插入:

5,二叉树的删除

算法步骤:

15.1,平衡二叉树完整代码如下:

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

typedef struct AVLNode{
    int high;
    int data;
    struct AVLNode *lchild,*rchild;
}AVLNode, *AVLTree; 

AVLTree Empty(AVLTree &T);//删除树 
void CreateAVL(AVLTree &T);//创建平衡二叉树
AVLTree Insert(AVLTree &T,int x);//插入节点
AVLTree adjust(AVLTree &T);//删除节点之后,对树的平衡进行调节
inline int Height(AVLTree T);//计算树的高度
AVLTree LL_Rotation(AVLTree &T);//LL旋转
AVLTree RR_Rotation(AVLTree &T);//RR旋转
AVLTree LR_Rotation(AVLTree &T);//LR旋转
AVLTree RL_Rotation(AVLTree &T);//RL旋转
AVLTree Delete(AVLTree &T,int x);//删除节点
void show(AVLTree T);//展示树的前序遍历,中序遍历和后序遍历
void Preorder(AVLTree T);//前序遍历方便看树的结果
void Inorder(AVLTree T);//中序遍历方便看树的结果
void Posorder(AVLTree &T);//后序遍历方便看树的结果
void updateHeight(AVLTree &T);//更新节点的深度 
 
 
int main()
{ 
    int x;
    AVLTree root=NULL;
    root=Empty(root);
    CreateAVL(root);
    show(root);
    cout << "请输入要删除的节点:" << endl;
    cin>>x;
    root=Delete(root,x);
    show(root);
    return 0;
}

AVLTree adjust(AVLTree &T)//删除结点后,需要判断是否还是平衡,如果不平衡,就要调整
{
    if(T==NULL) return NULL;
    if(Height(T->lchild)-Height(T->rchild)==2)//沿着高度大的那条路径判断
    {
        if(Height(T->lchild->lchild)>=Height(T->lchild->rchild))
            T=LL_Rotation(T);
        else
            T=LR_Rotation(T);
    }
    if(Height(T->rchild)-Height(T->lchild)==2)//沿着高度大的那条路径判断
    {
        if(Height(T->rchild->rchild)>=Height(T->rchild->lchild))
            T=RR_Rotation(T);
        else
            T=RL_Rotation(T);
    }
    updateHeight(T);
    return T;
}

AVLTree Delete(AVLTree &T,int x){
    if(T == NULL) return T;
    if(T->data == x){
        if(T->rchild==NULL){//如果该节点的右孩子为NULL,那么让其左子树代替双亲结点的位置 
            AVLTree temp=T;
            T=T->lchild;
            delete temp;
        }else{
            AVLTree temp = T;
            temp = temp->rchild;
            while(temp){//寻找直接后继 
                temp = temp->lchild;
            }
            T->data = temp->data;//让T的直接后继来覆盖T->data 
            T->rchild=Delete(T->rchild,T->data);//在T的右子树中删除T的直接后继 
               updateHeight(T);
        }
        return T; 
    }
    if(T->data > x){
        T->lchild = Delete(T->lchild,x); 
    } else{
        T->rchild = Delete(T->rchild,x);
    }
    updateHeight(T);
    if(T->lchild){//调一下左子树 
        T->lchild=adjust(T->lchild);
    }   
    if(T->rchild){//调一下右子树 
        T->rchild=adjust(T->rchild);
    }    
    if(T){//调一下根节点 
        T=adjust(T);
    } 
    return T;
}

void updateHeight(AVLTree &T){
    T->high = max(Height(T->lchild),Height(T->rchild))+1;//子树深度加上1等于本节点深度 
} 

inline int Height(AVLTree T)//计算高度
{
    if(T==NULL) return 0;
    return T->high;
}

AVLTree LL_Rotation(AVLTree &T){
    AVLTree temp=T->lchild;
    T->lchild=temp->rchild;
    temp->rchild=T;
    updateHeight(T);//更新高度
    updateHeight(temp);
    return temp;
}

AVLTree RR_Rotation(AVLTree &T){
    AVLTree temp=T->rchild;
    T->rchild=temp->lchild;
    temp->lchild=T;
    updateHeight(T);//更新高度
    updateHeight(temp);
    return temp;
}

AVLTree LR_Rotation(AVLTree &T){
    T = RR_Rotation(T->lchild);
    return LL_Rotation(T);
}

AVLTree RL_Rotation(AVLTree &T){
    T = LL_Rotation(T->rchild);
    return RR_Rotation(T);
}

AVLTree Insert(AVLTree &T,int x){
    AVLTree s;
    if(T == NULL){
        s =  new AVLNode;
        s->lchild = s->rchild = NULL;
        s->data = x;
        s->high = 1;
        T = s;
        return T; 
    }
    if(T->data == x) return T;//如果树中已经有这个节点那么直接返回什么也不做
    if(T->data > x){//如果节点根节点元素大于输入元素,就将其插入左子树 
        T->lchild = Insert(T->lchild,x);
        if(Height(T->lchild) - Height(T->rchild) == 2 ){
            if(x < T->lchild->data){
                T = LL_Rotation(T);
            } else{
                T = LR_Rotation(T);
            }
        }
    }else{
        T->rchild = Insert(T->rchild,x);
        if(Height(T->rchild) - Height(T->lchild) == 2){
            if(x < T->rchild->data){
                T = RL_Rotation(T);
            }else{
                T = RR_Rotation(T);
            }    
        }
    }
    updateHeight(T);
    return T;
}

void CreateAVL(AVLTree &T){
    int x;
    cout << "请输入要插入的元素节点(输入-1时,停止输入):" << endl; 
    cin >> x;
    while(x != -1){
        T = Insert(T,x);//先插入,然后再输入 
        cin >> x;
    }
}

AVLTree Empty(AVLTree &T){
    if(T==NULL) return NULL;
    Empty(T->lchild);
    Empty(T->rchild);
    delete T;
    return NULL;
}

void Preorder(AVLTree T){
    if(T==NULL) return ;
    cout<<T->data<<"\t"<<T->high<<endl;
    Preorder(T->lchild);
    Preorder(T->rchild);
}

 void Inorder(AVLTree T)//中序遍历方便看树的结果
{
    if(T==NULL) return ;
    Inorder(T->lchild);
    cout<<T->data<<"\t"<<T->high<<endl;
    Inorder(T->rchild);
}

 void Posorder(AVLTree &T)//后序遍历方便看树的结果
{
    if(T==NULL) return ;
    Posorder(T->lchild);
    Posorder(T->rchild);
    cout<<T->data<<"\t"<<T->high<<endl;
}

void show(AVLTree T)
{
    Preorder(T);
    cout<<endl;
    Inorder(T);
    cout<<endl;
    Posorder(T);
}

运行示例如下:

详细的算法代码分析:

1,在创建平衡二叉树的根节点的时候,我们一定要记得首先要让根节点为空,为此我们创建方法empty

AVLTree Empty(AVLTree &T){
    if(T==NULL) return NULL;
    Empty(T->lchild);
    Empty(T->rchild);
    delete T;
    return NULL;
}

2,插入节点算法

2.1,首先看传入的T指针是否为空,如果是NULL,那么创建此节点并且返回T。

2.2,如果T不为空那么进行比较:if(T->data == x) return T;//如果树中已经有这个节点那么直接返回什么也不做

2.3,如果T->data > x条件成立,那么将节点插入左子树,反之就插入右子树,详细的插入节点解析如下:

2.4,插入完节点之后我们更新T节点的深度并且返回T指针

在插入算法中我们会看到如下这样代码:

if(Height(T->lchild) - Height(T->rchild) == 2 ){
            if(x < T->lchild->data){
                T = LL_Rotation(T);
            } else{
                T = LR_Rotation(T);
            }
        }

这样的步骤是为再插入的过程中防止以下情况出现,在我们插入完3节点之后发现,Height(T->lchild) - Height(T->rchild) == 2条件成立,那么就会开始LL旋转或者LR旋转。

3,删除节点算法:

3.1,首先拿到传入的T节点,如果为空就直接返回,如果不为空就进行以下比较

3.2,如果T->data == x条件成立,那么就直接使用之前提到到两种方法,子承父业或者是使用直接前驱代替或者是使用直接后继代替,这里是使用直接后继去代替,因为这两宗方法在创建BST树的时候就已经提到,所以在此就不做赘述。

如果条件不成立。那么就进行以下比较。

3.3,如果T->data > x条件成立,那么就在左子树中递归删除,反之,就在右子树中递归删除。删除完之后,更新T节点的深度。

if(T->data > x){
        T->lchild = Delete(T->lchild,x); 
    } else{
        T->rchild = Delete(T->rchild,x);
    }
    updateHeight(T);

3.4,当我们完成删除之后,我们需要去检查树是否还是处于平衡状态,所以我们递归调整左子树,右子树,和根节点

if(T->lchild){//调一下左子树 
        T->lchild=adjust(T->lchild);
    }   
    if(T->rchild){//调一下右子树 
        T->rchild=adjust(T->rchild);
    }    
    if(T){//调一下根节点 
        T=adjust(T);
    } 

4,调整树算法:

AVLTree adjust(AVLTree &T)//删除结点后,需要判断是否还是平衡,如果不平衡,就要调整
{
    if(T==NULL) return NULL;
    if(Height(T->lchild)-Height(T->rchild)==2)//沿着高度大的那条路径判断
    {
        if(Height(T->lchild->lchild)>=Height(T->lchild->rchild))
            T=LL_Rotation(T);
        else
            T=LR_Rotation(T);
    }
    if(Height(T->rchild)-Height(T->lchild)==2)//沿着高度大的那条路径判断
    {
        if(Height(T->rchild->rchild)>=Height(T->rchild->lchild))
            T=RR_Rotation(T);
        else
            T=RL_Rotation(T);
    }
    updateHeight(T);
    return T;
}

5,LL旋转算法:

AVLTree LL_Rotation(AVLTree &T){
    AVLTree temp=T->lchild;
    T->lchild=temp->rchild;
    temp->rchild=T;
    updateHeight(T);//更新高度
    updateHeight(temp);
    return temp;
}

6,LR旋转算法:

AVLTree LR_Rotation(AVLTree &T){
    T = RR_Rotation(T->lchild);
    return LL_Rotation(T);
}

关于RR旋转和RL旋转同理,不做赘述。

16,排序篇

16.1,插入排序(稳定)

1,插入排序

0

算法步骤

拿已经有序的有序序列的最后一位和无序序列的第一位进行比较,这里我们首先拿有序序列最后一位r[1]12和无序序列第一位r[2]2比较,发现2

之后12再和16比较同理,然后30和28比较,30后移,28存入r[0],然后28在和16比较,之后28直接放在16后面。

稳定排序:在序列中有两个相等的关键字A1==A2,排序之前A1在A2前面,排序之后A1仍然在A2的前面,这就是稳定排序。

不稳定排序:在序列中有两个相等的关键字A1==A2,排序之前A1在A2前面,排序之后A1在A2的后面去了,这就是不稳定稳定排序。

如下:

非递减:就是允许相等的递增

非递增:就是允许相等的递减

递增:就是不允许相等的递增

递减:就是不允许相等的递减

最好的情况是O(n),最坏的情况算法复杂度O(n^2)

平均情况算法复杂度O(n^2).

16.2,插入排序完整代码

#include <iostream>
using namespace std;
#define Maxsize 100

void StraightInsertSort(int r[],int n)  //直接插入排序
{
     int i,j;
     for(i = 2;i <= n;i++){//注意这里的条件是i <= n,因为我们使用r[0],作为哨兵,所以假如我们存入6个数据 
         if(r[i-1] > r[i]){//我们会一直存储到r[6]
         r[0] = r[i];
        r[i] = r[i-1];
        for(j = i-2;r[0] < r[j];j--){
            r[j+1] = r[j];
        }
        r[j+1] = r[0];    
        }
     }
}

int main()
{
    int i,n,r[Maxsize+1];
    cout<<"请输入数列中的元素个数n为:"<<endl;
    cin>>n;
    cout<<"请依次输入数列中的元素:"<<endl;
    for(i=1;i<=n;i++)
       cin>>r[i];
    StraightInsertSort(r,n);
    cout<<"直接插入排序结果:"<<endl;
    for(i=1;i<=n;i++)
       cout<<r[i]<<" ";
    return 0;
}

运行示例:

 

16.3,冒泡排序(稳定)

虽然冒泡排序的最好和最坏的算法时间复杂的和插入排序一样,但是,效率远远的低于插入排序,因为,冒泡排序需要不断地交换位置。

16.4,冒泡排序完整代码

#include<iostream>
using namespace std;
#define Maxsize 100

void BubbleSort(int r[],int n){
    int i,j,temp;
    bool flag = true;
    i = n-1;
    while(i>0 && flag){
        flag = false;
        for(j = 0;j < i;j++){
            if(r[j] > r[j+1]){
                flag = true;
                temp = r[j];
                r[j] = r[j+1];
                r[j+1] = temp; 
            }
        }
        for(j = 0;j <= i;j++){
            cout << r[j] << "\t";
        }
        cout << endl;
    }
} 

int main()
{
    int i,n,r[Maxsize];
    cout<<"请输入数列中的元素个数n为:"<<endl;
    cin>>n;
    cout<<"请依次输入数列中的元素:"<<endl;
    for(i=0;i<n;i++){
        cin>>r[i];
    }
    BubbleSort(r,n);
    cout<<"冒泡排序结果:"<<endl;
    for(i=0;i<n;i++)
       cout<<r[i]<<" ";
    return 0;
}

 

运行示例:

16.5,快速排序(不稳定)

1,快排

最坏的情况就是分解之后一边是0,还有一边是n-1,也就是说,一边是基准元素,还有有一边是比基准元素大的一个子序列(这是选取第一个元素作为基准元素的情况),如下图,5(之前的基准元素)和12都是基准元素,还有一边就是比基准元素大的子序列

那么,我们该怎么来选择基准元素呢?有以下五种情况。有时候我们在写算法的时候,使用快排是无法通过的。这是因为基准元素的选取不到位。

算法步骤:

分解:

递归:

算法分析:

在最好的情况下,快排的T(n)为

由于算法在数据规模等于1的时候停止,所以n/2^x = 1.

空间复杂度就是递归树的深度

在最坏的情况下,快排的T(n)为

空间复杂度:

平均情况下,算法复杂度仍然是O(nlogn)

算法改进:

但是没有改变算法的阶,仍然是O(nlogn)

16.6,快速排序完整代码

#include <iostream>
using namespace std;

int Partition1(int r[],int low,int high){//改良前 
    int i = low,j = high,pivot = r[low];
    while(i < j){
         while(i<j && r[j]>pivot) j--;//从左扫描 
         if(i < j){
             swap(r[i++],r[j]); 
        }
        while(i<j && r[i]<=pivot) i++;//从右扫描
        if(i < j){
            swap(r[i],r[j--]); 
        }  
    }
    return i;//返回最终划分完成后基准元素所在的位置
}
int Partition2(int r[],int low,int high){//改良后
    int pivot = r[low],i = low+1,j = high;
    while(i < j){
        while(i<j && pivot > r[i]) i++;
        while(i<j && pivot <= r[j]) j--;
        if(i < j){//时刻保持i < j
            swap(r[i++],r[j--]);
        }
    }
    if(pivot < r[i]){
        swap(r[i-1],r[low]);
        return i-1; 
    } 
    swap(r[i],r[low]);
    return i;
}


void QuickSort(int a[],int low,int high){
    int mid;
    if(low < high){//递归使用的一般是if语句来控制程序的结束 
        cout << mid << " \t"; 
        mid = Partition2(a,low,high);//首先划分数列 
        QuickSort(a,low,mid-1);         //左区间递归快排
        QuickSort(a,mid+1,high);     //区间递归快排 
    }
}


int main(){
    int a[100];
    int i,n;
    cout<<"请先输入要排序的数据的个数:";
    cin>>n;
    cout<<"请输入要排序的数据:";
    for(i=0;i<n;i++)
        cin>>a[i];
    cout<<endl;
    QuickSort(a,0,n-1);
    cout<<"排序后的序列为:"<<endl;
    for(i=0;i<n;i++)
        cout<<a[i]<<" " ;
    cout<<endl;
    return 0;
}

运行示例:

16.7,合并排序(稳定)

1,合并排序

和快排不同,合并排序不需要去选择基准元素,而是直接生硬的将数列一分为二。

如下图:

合并操作所使用的辅助数组需要将里面的数据放回原来的数组,之后空的辅助数组再用于下一次的合并操作

排序完了再去合并

合并排序没有最好和最坏的情况。

空间复杂度

16.8,合并排序完整代码

#include <iostream>
using namespace std;

void Merge(int A[], int low, int mid, int high){
    int *B = new int[high-low+1];
    int i = low,j = mid+1,k = 0;
    while(i<=mid && j<=high){
        if(A[i]<=A[j]){
            B[k++] = A[i++];
        }else{
            B[k++] = A[j++];
        }
    }
    while(i<=mid) B[k++]=A[i++];//将数组中剩下的元素放置B中
    while(j<=high) B[k++]=A[j++];
    for(i=low, k=0; i<=high; i++){//将B中排序好的数组放回A数组 
        A[i]=B[k++];
    }
    delete []B;//释放空间
}

void MergeSort(int A[], int low, int high){//合并排序 
    if(low<high){//递归使用的一般是if语句来控制程序的结束 
        int mid=(low+high)/2;//取中点
        MergeSort(A, low, mid);//对A[low:mid]中的元素合并排序
        MergeSort(A, mid+1, high);//对A[mid+1:high]中的元素合并排序
        Merge(A, low, mid, high);//合并
    }
}

int main()
{
    int n, A[100];
    cout<<"请输入数列中的元素个数n为:"<<endl;
    cin>>n;
    cout<<"请依次输入数列中的元素:"<<endl;
    for(int i=0; i<n; i++)
       cin>>A[i];
    MergeSort(A,0,n-1);
    cout<<"合并排序结果:"<<endl;
    for(int i=0;i<n;i++)
       cout<<A[i]<<" ";
    cout<<endl;
    return 0;
}

运行示例:

16.9,选择排序(不稳定) 

1,选择排序

其实和冒泡排序相反。冒泡排序是每次将一个最大的元素放到最后,而选择排序是每次将最小的元素放到最前

如上图:可以看到选择排序是不稳定的。

时间复杂度O(n^2)

空间复杂度O(1)

不稳定

16.10,选择排序完整代码如下:

#include<iostream>
using namespace std;
#define Maxsize 1000

void SelectSort(int r[],int n){//选择排序 
    int i,j,k;
    for(i = 0;i<n;i++){
        k = i;
        for(j = i+1;j<n;j++){
            if(r[k] > r[j]){
                k=j;            //将本次遍历最小的元素的下标给k 
            }
        }
        swap(r[i],r[k]);
        for(j = i;j<n;j++){
            cout << r[j] << "\t";
        }
        cout << endl;
    } 
}

int main()
{
    int i,n,r[Maxsize];
    cout<<"请输入数列中的元素个数n为:"<<endl;
    cin>>n;
    cout<<"请依次输入数列中的元素:"<<endl;
    for(i=0;i<n;i++)
       cin>>r[i];
    SelectSort(r,n);
    cout<<"选择排序结果:"<<endl;
    for(i=0;i<n;i++)
       cout<<r[i]<<" ";
    return 0;
}

代码很简单不多讲。

运行示例:

 

16.11,堆排序(不稳定)

由图可知,通过儿子或者父亲的下标找父亲或者找儿子都是很简单的。但是前提是,这颗树是一个完全二叉树。父亲节点的下标是i那么左孩子是2i,右孩子就是2i+1

1.1,堆下沉

只需要调整堆顶即可。

1.2,初始化堆

如下图,对无序序列的构建初始堆的操作。

注意,虽然我们将无序序列以完全二叉树的形式呈现,但是,我们实际上还是在数组上操作。

算法分析:

时间复杂度O(nlogn)

空间复杂度O(1)

不稳定

16.12,堆排序完整代码如下:

#include<iostream>
using namespace std;
#define maxN 1000
int r[maxN];


void Sink(int k,int n){//下沉 
    int j;
    while(2*k <= n){
        j = 2*k;
        if(j<n&&r[j]<r[j+1]){
            j++;
        } 
        if(r[n]>r[j]){
            break;
        }else{
            swap(r[n],r[j]);
        }
        k = j;
    }
}

void CreatHeap(int n){
    int i;
    for(i=n/2;i>=1;i--){
        Sink(i,n);
    }
}

void HeapSort(int n)//堆排序
{
    CreatHeap(n);//构建初始堆
    while(n>1)
    {
        swap(r[1],r[n--]);//堆顶和最后一个记录交换,交换后n减1
        Sink(1,n);//堆顶下沉
    }
}
void print(int n)//输出
{
    for(int i=1;i<=n;i++)
        cout<<r[i]<<"\t";
    cout<<endl;
}


int main()
{
    int n;
    cout << "请输入待排序记录个数:" << endl;
    cin>>n;
    cout<<"请输入n个整数:"<<endl;
    for(int i=1;i<=n;i++)
        cin>>r[i];
    HeapSort(n);//堆排序
    print(n);
    return 0;
}

运行示例:

我们来简单用图来演示一下这段代码的排序流程:

够形象了吧!手都要画到抽筋。

当然,由于此数列有序递减,所以初始化堆的过程一直都在break,因为满足条件r[n]>r[j],所以我们直接来到下沉堆顶的操作。

16.13,桶排序(稳定)

桶间有序,桶内无序

16.14,基数排序(稳定) 

最大位数有几位,那么就需要排序几趟。

z字型收集

注意分配和收集的有序性。

可以使用队列的方式来保持有序性。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值