目录
前言
以下算法和数据结构代码都可以在本人的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),插入的时间为
但是,如果我们查找的元素在树中已经存在,那么该怎么办呢?处理的方法有两种,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,插入排序
算法步骤
拿已经有序的有序序列的最后一位和无序序列的第一位进行比较,这里我们首先拿有序序列最后一位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字型收集
注意分配和收集的有序性。
可以使用队列的方式来保持有序性。