目录
内部排序: 指的是待排序记录存放在计算机随机存储器中进行的排序过程。1-5
外部排序: 指的是待排序记录的数量很大,以致内存一次不能容纳全部记录,在排序过程中尚需对外存进行访问的排序过程。6
时间复杂度比较
算法 | 最大时间 | 平均时间 | 最小时间 | 辅助空间代价 | 稳定性 |
直接插入排序 | Θ(n2) | Θ(n2) | Θ(n) | Θ(1) | 稳定 |
希尔排序 | Θ(n3/2) | Θ(n3/2) | Θ(n3/2) | Θ(1) | 不稳定 |
冒泡排序 | Θ(n2) | Θ(n2) | Θ(n) | Θ(1) | 稳定 |
快速排序 | Θ(n2) | Θ(nlog n) | Θ(nlog n) | Θ(log n) | 不稳定 |
选择排序 | Θ(n2) | Θ(n2) | Θ(n2) | Θ(1) | 不稳定 |
堆排序 | Θ(nlog n) | Θ(nlog n) | Θ(nlog n) | Θ(1) | 不稳定 |
归并排序 | Θ(nlog n) | Θ(nlog n) | Θ(nlog n) | Θ(n) | 稳定
|
基数排序 | Θ(d·(n+r)) | Θ(d·(n+r)) | Θ(d·(n+r)) | Θ(n+r) | 稳定 |
1. 插入排序
-
1.1 直接插入排序
定义:将一个记录插入到已排好序的有序表中,从而得到一个新的有序表。
性质:稳定;
空间代价:Θ(1);
时间代价:
最佳情况:n-1次比较,2(n-1)次移动,Θ(n)
最差情况:Θ(n2)
比较次数为
移动次数为
平均情况:Θ(n2)
void Sort1(int Data[],int n)//直接插入排序
{
int p,i;
for(p = 1;p<n;p++)
{
int temp=Data[p];
i=p-1;
while(i>=0&&Data[i]>temp)
{
Data[i+1] = Data[i];
i--;
}
Data[i+1] = temp;
}
}
-
1.2 折半插入排序
定义:在插入第i个记录时,前面的记录已经是有序的了,用二分法查找第i个记录的正确位置(把1.1的顺序查找换为二分法)
性质:稳定
折半插入排序的时间复杂度仍为O(n2)
void Sort2(int Data[],int n)//折半插入排序
{
int left,mid,right,p;//
for(p=1;p<n;p++)
{
int temp=Data[p];//保存待插入数据
left=0,right=p-1;
while(left<=right)
{
mid=(left+right)/2;//求出中心点
if(Data[mid] > temp)
{
right = mid-1;
}
else
{
left = mid+1;
}
}
for(int i=p-1;i>=left;i--)
{
Data[i+1]=Data[i];
}
Data[left]=temp;//将待插入元素插入到有序表中
}
}
-
1.3 希尔排序
定义:
直接插入排序的两个性质: 在最好情况(序列本身已是有序的)下时间代价为Θ(n) ;对于短序列,直接插入排序比较有效
Shell排序有效地利用了直接插入排序的这两个性质
- 先将序列转化为若干小序列,在这些小序列内进行插入排序
- 逐渐扩大小序列的规模,而减少小序列个数,使得待排序序列逐渐处于更有序的状态
- 最后对整个序列进行扫尾直接插入排序,从而完成排序
“增量每次除以2递减”的Shell 排序
性质:不稳定
空间代价:Θ(1)
时间代价:希尔排序的时间复杂性在O(nlog2n)和O(n2 )之间,大致为O(n1. 3)。
void Sort3(int Data[],int n)//希尔排序
{
int d=n/2;
while(d>=1)
{
for(int k=0;k<d;k++)
{
for(int i=k+d;i<n;i+=d)//直接插入排序
{
int temp=Data[i];
int j=i-d;
while(j>=k&&Data[j]>temp)
{
Data[j+d]=Data[j];
j-=d;
}
Data[j+d]=temp;
}
}
d=d/2;
}
}
#include<iostream>
using namespace std;
int main()
{
int a[5]={11,24,65,35,91};
int b[5]={2,65,98,74,15};
int c[7]={65,34,25,87,12,38,56};
Sort1(a,5);
for(int i=0;i<5;i++)
{
cout<<a[i]<<" ";
}
cout<<endl;
Sort2(b,5);
for(int j=0;j<5;j++)
{
cout<<b[j]<<" ";
}
cout<<endl;
Sort3(c,7);
for(int k=0;k<7;k++)
{
cout<<c[k]<<" ";
}
return 0;
}
2. 交换排序
-
2.1 冒泡排序
定义:不停地比较相邻的记录,如果不满足排序要求,就交换相邻记录,直到所有的记录都已经排好序。
注意:避免不必要的比较。检查每次冒泡过程中是否发生过交换,如果没有,则表明整个数组已经排好序了,排序结束。
性质:稳定性:稳定
空间代价:Θ(1)
时间代价分析:
比较次数 : 最少:Θ(n) 最多:
交换次数: 最多:Θ(n2) 最少:0 平均:Θ(n2)
void BubbleSort(int Data[],int n)
{
int flag=0;
int i,j;
for(i=0;i<n;i++)
{
flag=0;
for(j=1;j<n-i;j++)
{
if(Data[j]<Data[j-1])
{
flag=1;
int temp=Data[j];
Data[j]=Data[j-1];
Data[j-1]=temp;
}
}
if(flag==0)
{
return ;
}
}
}
-
2.2 快速排序
定义:
- 选择轴值(pivot)
- 将序列划分为两个子序列L和R,使得L中所有记录都小于或等于轴值,R中记录都大于轴值
- 对子序列L和R递归进行快速排序
主要思想:分——划分子问题;治——求解子问题(子问题不重叠);合——综合解
轴值选择:尽可能使L,R长度相等
选择策略: 选择最左边记录(第一个记录);随机选择;选择平均值
分割过程:轴值位于正确位置,分割后使得 L中所有记录小于轴值 R中记录大于轴值
性质:不稳定
最佳情况: 时间代价:Θ(nlog n) 空间代价:Θ(log n)
最差情况: 时间代价: Θ(n2) 空间代价: Θ(n)
平均情况: 时间代价:Θ(nlog n) 空间代价:Θ(log n)
最好的情形(左、右子区间的长度大致相等)
最坏的情形(每次能划分成两个子区间,但其中一个是空 或 要排序的数据已基本有序)
可能优化: 轴值选择、小子串不递归、消除递归
快速排序的过程:
第一趟排序后 13 38 27 49 76 97 65 52
int Partition(int Data[],int left,int right)//快速排序分割策略一
{
//实现对Date[left]到date[right]的分割操作 并返回划分后周元素对应的位置
int pivot = Data[left];//选择最左边的为轴元素
while(left<right)
{
while(left<right &&Data[right]>pivot)//控制right指针的移动
{
right--;
}
Data[left] = Data[right];//将指针指向的数据移动到left位置
while(left<right &&Data[left]<=pivot)//控制left指针移动
{
left++;
}
Data[right] = Data[left];//将left指向的数据移动到right位置
}
Data[left] = pivot;//将轴元素放到left位置
return left; //返回轴元素的新位置 实现分治
}
void QuickSort(int Data[],int left,int right)
{
//用分治法实现快速排序算法
if(left<right)
{
int p=Partition(Data,left,right);//实现分割并找到分离的位置
QuickSort(Data,left,p-1);
QuickSort(Data,p+1,right);
}
}
int Partition1(int Data[],int start,int end)//快速排序分割策略二
{
//实现对Data[start]到date[end]的分割操作,并返回划分后轴元素对应的位置
int pivot = Data[start];
int left = start+1,right = end;//初始化 left right
int temp;//交换变量
while(left<=right)
{
while(left<=right &&Data[left]<=pivot)//控制left指针的移动
{
left++;
}
while(left<=right && Data[right]>pivot)//控制right指针的移动
{
right--;
}
if( left<right )
{
//swap(Data[right],Data[left]);//交换Data[right]和Date[left]
temp=Data[right];
Data[right]=Data[left];
Data[left]=temp;
left++;
right--;
}
//swap(Data[start],Data[right]);//交换Data[right]和轴元素Data[start]
}
temp=Data[right];
Data[right]=Data[start];
Data[start]=temp;
return right;//返回轴元素的新位置 实现分治
}
void QuickSort1(int Data[],int left,int right)
{
//用分治法实现快速排序算法
if(left<right)
{
int p=Partition1(Data,left,right);//实现分割并找到分离的位置
QuickSort1(Data,left,p-1);
QuickSort1(Data,p+1,right);
}
}
#include<iostream>
using namespace std;
int main()
{
int a[8]={45,32,61,98,74,17,22,53};
int b[8],c[8];
for(int j=0;j<8;j++)
{
b[j]=a[j];
c[j]=a[j];
}
BubbleSort(a,8);
cout<<"冒泡排序 ";
for(int i=0;i<8;i++)
{
cout<<a[i]<<" ";
}
cout<<endl;
cout<<"快排1: ";
QuickSort(b,0,7);
for(int i=0;i<8;i++)
{
cout<<b[i]<<" ";
}
cout<<endl;
cout<<"快排2: ";
QuickSort1(c,0,7);
for(int i=0;i<8;i++)
{
cout<<c[i]<<" ";
}
return 0;
}
3. 选择排序
-
3.1 直接选择排序
选出剩下的未排序记录中的最小记录,然后直接与数组中第i个记录交换
性质:不稳定
空间代价: Θ(1)
时间代价 : 比较次数:Θ(n2) 交换次数:n-1 总时间代价:Θ(n2)
void SelectionSort(int Data[],int n)
{
for(int i=1;i<n;i++)
{
int k=i-1;//假定第i-1个为序列中最小的元素
for(int j=i;j<n;j++)
{
if(Data[j]<Data[k])//记录最小元素的下标
k=j;
}
if(k!=i-1)//如果k改变
{
int t=Data[k];
Data[k]=Data[i-1];
Data[i-1]=t;
}
}
}
-
3.2 堆排序
基于最大值堆来实现
- 性质:不稳定
- 建堆:Θ(n) 删除堆顶重新建:Θ(log n)
- 一次建堆,n次删除堆顶
- 总时间代价为Θ(nlog n) 空间代价为Θ(1)
template <class Record>
void sort(Record Array[], int n) {
int i;
MaxHeap<Record> max_heap = MaxHeap<Record>(Array,n,n); // 建堆
// 算法操作n-1次,最小元素不需要出堆
for (i = 0; i < n-1; i++)
// 依次找出剩余记录中的最大记录,即堆顶
max_heap. RemoveMax();
}
/*最大堆的相关代码*/
/*定义堆,封装初始化,插入,删除栈顶元素的操作*/
#include<iostream>
using namespace std;
class Maxheap
{
private:
int* heap;
int n;
int cs;
public:
Maxheap()
{
heap=NULL;
n=0;
cs=0;
}
Maxheap(int maxsize=100)
{
heap=new int[maxsize];
n=maxsize;
cs=0;
}
bool Insert(int x)
{
cs++;
heap[cs-1]=x;
int i=cs-1;
shiftup(i);
}
void shiftup(int i)
{
int parent,temp;
while(i>0)
{
parent=(i-1)/2;
if(heap[i]>heap[parent])
{
temp=heap[i];
heap[i]=heap[parent];
heap[parent]=temp;
i=parent;
}
else
break;
}
}
void initialize(int a[],int na)
{
for(int i=0;i<na;i++)
heap[i]=a[i];
cs=na;
for(int j=(cs-2)/2;j>=0;j--)
shiftdown(j);
}
void shiftdown(int i)
{
int lchild,rchild,temp;
while(1)
{
lchild=2*i+1;
rchild=2*i+2;
if(lchild<cs)
{
if(rchild<cs&&heap[lchild]<heap[rchild])
{
if(heap[i]<heap[rchild])
{
temp=heap[i];
heap[i]=heap[rchild];
heap[rchild]=temp;
i=rchild;
}
else
break;
}
else
{
if(heap[i]<heap[lchild])
{
temp=heap[i];
heap[i]=heap[lchild];
heap[lchild]=temp;
i=lchild;
}
else
break;
}
}
else
break;
}
}
void Delete(int temp)
{
int m,p;
int item;
m=cs-1;
for(int i=0; i<cs; i++)
if(temp==heap[i])
{
p=i;
break;
}
item=heap[p];
heap[p]=heap[m];
heap[m]=item;
cs--;
shiftdown(p);
}
void display()
{
cout<<"堆内元素为:"<<endl;
for(int i=0;i<cs;i++)
cout<<heap[i]<<endl;
}
};
int main()
{
Maxheap heap(100);
int *a,num,temp,x;
cout<<"输入堆元素个数"<<endl;
cin>>num;
a=new int[num];
cout<<"输入堆元素"<<endl;
for(int i=0;i<num;i++)
{
cin>>temp;
a[i]=temp;
}
heap.initialize(a,num);
heap.display();
cout<<"输入要插入的元素"<<endl;
cin>>x;
heap.Insert(x);
heap.display();
cout<<"输入要删除的元素"<<endl;
cin>>x;
heap.Delete(x);
heap.display();
}
4. 归并排序
简单地将原始序列划分为两个子序列
分别对每个子序列递归排序
最后将排好序的子序列合并为一个有序序列,即归并过程
性质:稳定性:稳定
算法最坏情况的复杂度为
算法需要
的辅助空间 因为需要一个与原始序列同样大小的辅助序列。这是此算法的缺点。
#include<iostream>
using namespace std;
void Merge(int Data[],int start,int end,int mid)
{
int len1=mid-start+1;
int len2=end-mid;
int i,j,k;
int *left=new int[len1];
int *right=new int[len2];
for(i=0;i<len1;i++)
left[i]=Data[i+start];
for(i=0;i<len2;i++)
right[i]=Data[i+mid+1];
i=0,j=0;
for(k=start;k<end;k++)
{
if(i==len1||j==len2)
break;
if(left[i]<=right[j])
Data[k]=left[i++];
else
Data[k]=right[j++];
}
while(i<len1)
Data[k++]=left[i++];
while(j<len2)
Data[k++]=left[j++];
delete []left;
delete []right;
}
void MergeSort(int Data[],int start,int end)
{
if(start<end)
{
int mid=(start+end)/2;
MergeSort(Data,start,mid);
MergeSort(Data,mid+1,end);
Merge(Data,start,end,mid);
}
}
int main()
{
int b[6]={8,4,5,6,2,1};
cout<<"归并排序:"<<endl;
MergeSort(b,0,5);
for(int i=0;i<6;i++)
cout<<b[i]<<" ";
return 0;
}
5. 基数排序
假设长度为n的序列 R={ r0,r1,…,rn-1 }
记录的排序码K包含d个子排序码 K=(kd-1,kd-2,…,k1,k0 )
R对排序码(kd-1,kd-2,…,k1,k0 )有序
对于任意两个记录R i,R j (0 ≤ i< j ≤ n-1),都满足(k i ,d-1,k i ,d-2, …,k i ,1,k i,0 ) ≤ (k j ,d-1,k j, d-2,…,k j,1,k j,0 )
其中k d-1称为最高排序码 k 0称为最低排序码
基数排序分为两类:高位优先法MSD,低位优先法LSD
低位优先法LSD:
从最低位k0开始排序 对于排好的序列再用次低位k1排序;
依次重复,直至对最高位kd-1排好序后,整个序列成为有序的
这是一个分、收;分、收;…;分、收的过程 比较简单,计算机常用
性质:稳定性:稳定(一直前后有序)
特点:不用比较和移动,改用分配和收集,时间效率高!
时间复杂度为:O (mn) 空间复杂度:O(n)
6. 外部排序
-
6.1 置换选择排序
-
6.2 多路归并