8 排序的基本概念
将各个元素按照关键字大小进行升序降序排序
排序算法的评价指标-----算法的稳定性
----- 时间复杂度、空间复杂度
内部排序
数据都在内存中(**关注时间复杂度、空间复杂度**)
插入排序
算法思想:每次将一个待排序的记录按其关键字大小插入到前面已经排好序的子序列中,直到全部记录插入完成。
//直接插入排序(法一)
void InsertSort(int A[],int n){
int i,j,temp;
for(i=1;i<n;i++){ //将各元素插入到已排好序的序列当中
if(A[i]<A[i-1]){ //若A[i]关键字小于前驱
temp = A[i]; //用temp暂存A[i]的值
for(j=i-1;j>=0&&A[j]>temp;j--) //检查所有前面已排好序的元素
A[j+1] = A[j]; //所有大于temp的元素都向后移动
A[j+1] = temp; //复制到插入位置
}
}
}
//直接插入排序(带哨兵)
void InsertSort(int A[],int n){
int i,j;
for(i=2;i<n;i++){ //依次将A[2]~A[n]插入到前面已排序序列
if(A[i]<A[i-1]){ //若A[i]关键字小于其前驱,将A[i]插入有序表
A[0] = A[i]; //复制为哨兵,A[0]不存放元素
for(j=i-1;A[0]<A[j];j--) //从后往前找待插入位置
A[j+1] = A[j]; //向后诺位
A[j+1] = A[0]; //复制到插入位置
}
}
}
空间复杂度:O(1)
时间复杂度:主要来自对比关键字,移动元素若有n个元素,则需要n-1趟处理。
最好情况:O(n) 原本有序
最坏情况:O(n^2) 原本逆序
平均情况:O(n^2)
稳定性:稳定
优化–折半插入排序
思路:先用折半查找找到应该插入的位置,再移动元素
注: 一直到low>high才停止折半查找,当mid所指元素等于当前元素时,即A[mid] = A[0],为保证算法稳定性,应该继续在mid所指位置右边寻找插入位置
也可应用于链表(?)
//优化---折半插入排序
void InsertSort(int A[],int n){
int i,j,low,high,mid;
for(i=2;i<=n;i++){ //依次将A[2]~A[n]插入到前面已排好序的序列
A[0] = A[i]; //将A[i]暂存到A[0]
low = 1;
hight = i-1; //设置折半查找的范围
while(low <= high){ //折半查找(默认递增有序)
mid = (low+high)/2; //取中间点
if(A[mid]>A[0])
high = mid-1; //查找左半子表
else low = mid+1; //查找右半子表
}
for(j = i-1;j>=high+1;j--)
A[j+1] = A[j]; //统一后移元素,空出插入位置
A[high+1] = A[0] //插入操作
}
}
时间复杂度:O(n^2)
希尔排序
对插入排序的优化
思想:先追求表中元素部分有序,再整体有序。
先将排序分割成若干个特殊子表,对各个子表进行直接插入排序,缩小增量d,重复上述过程,直到d为 1。
//希尔排序
void ShellSort(int A[],int n){
int d,i,j;
//A[0]只是暂存单元(不是哨兵),当j<=0时,插入位置已到
for(d = n/2;d>=1;d=d/2){ //步长变化
for(i = d+1;i<=n;i++){
if(A[i]<A[i-d]){ //将A[i]插入到有序子表中
A[0] = A[i]; //暂存A[i]
for(j = i-d;j>0 && A[0]<A[j];j--)
A[j+d] = A[j]; //记录后移,查找插入位置
A[j+d] = A[0]; //插入
}
}
}
}
空间复杂度:O(1)
时间复杂度:最坏O(n^2)
当n在某个范围内时,可达O(n^1.3)
但优于直接插入排序
稳定性:不稳定
适用性:仅适用于顺序表,不适用于链表
高频:给出增量序列,分析每一趟排序后的状态
交换类排序(冒泡、快速)
冒泡排序
从后往前,两两比较相邻元素的值,若为逆序,则交换它们,直到序列比较完,称这样过程为一趟冒泡排序。
每趟排序都能够得到一个最小的值
两值相等,无需交换位置
//冒泡排序
void BubbleSort(int A[],int n){
for(int i = 0;i<n-1;i++){
bool flag = false; //表示本趟冒泡是否发生交换的标志
for(j = i-1;j>i;j++){ //一趟冒泡排序
if(A[j-1]>A[j]){ //若为逆序
swap(A[j-1],A[j]); //交换
flag = true;
}
}
if(flag == false)
return; //本趟遍历没有发生交换,说明表已经有序
}
}
void swap(int &a,int &b){
int temp = a;
a = b;
b = temp;
}
注:只有A[j-1]>A[j]时交换,算法才是稳定的
空间复杂度:O(1)
时间复杂度:最坏情况(逆序):O(n^2) 比较次数=交换次数= (n-1)n/2
最好情况:O(n) 比较次数:n-1 交换次数:0
平均:O(n^2)
稳定性:稳定
该思想也适用于链表
若某一趟排序过程未发生交换,则算法可以提前结束(flag == false)
快速排序(*****)
算法思想:在待排序表中任取一个元素pivot作为枢轴(基准),
通过一趟排序将待排序表分为独立的两部分[1...k-1],[k+1...n],
使得L[1...k-1]中的所有元素都小于pivot,L[k+1...n]中的所有元素都大于pivot;再重复上述过程。
//快速排序
void QuickSort(int A[],int low,int high){
if(low < high){
int pivotpos = Partition(A,low,high); //划分
QuickSort(A,low,pivotpos-1); //划分左子表
QuickSort(A,pivotpos+1,high); //划分右子表
}
}
//用第一个元素将待排序序列划分为左右两个部分
int Partition(int A[],int low,int high){
int pivot = A[low]; //第一个元素作为枢纽
while(low<high){ //用low、high搜索枢轴的最终位置
while(low<high&&A[high]>=pivot)
--high;
A[low] = A[high]; //比枢轴小的元素移动到左端
while(low<high&&A[low]<=pivot)
++low;
A[high] =A[low]; //比枢轴大的元素移动到右端
}
A[low] = pivot; //枢轴元素存放到最终位置
return low; //返回存放枢轴的最终位置
}
算法效率分析:Partition函数:O(n)
QuickSort: O(log2n)
最好时间复杂度: O(nlog2n)
最坏时间复杂度:O(n^2)
平均时间复杂度:O(nlog2n)
稳定性:不稳定
最好空间复杂度:O(log2n)
最坏空间复杂度:O(n)
注:n个结点的二叉树 最小高度 = log2n + 1
最大高度:n
算法表现取决于递归深度,若每次划分越均匀,则递归深度越低。
划分不均匀,递归深度越深。*斜体样式*
快速排序算法优化思路:尽量选择可以把数据中分的枢轴元素
eg:选头、中、尾三个位置的元素,取中间值作为枢轴元素;
随机选一个元素作为枢轴元素
选择排序(简单选择,堆)
简单选择排序
算法思想:每一趟在待排序元素中选取关键字最小的元素加入有序子序列
//简单选择排序
void SelectSort(int A[],int n){
for(int i = 0;i<n-1;i++){
int min = i;
for(int j = i+1;j<n;j++)
if(A[j]<A[min])
min = j;
if(min!=i)
swap(A[i],A[min]);
}
}
//交换
void swap(int &a,int &b){
int temp = a;
a = b;
b = temp;
}
空间复杂度:O(1)
时间复杂度:O(n^2)
稳定性:不稳定
适用性:顺序表,链表均可
无论有序,无序,一定需要n-1趟处理
总共需要对比关键字n(n-1)/2次
元素交换次数<n-1
堆排序(*****)
什么是“堆”?
若n个关键字序列L[1...n],满足以下某条性质,则称为堆
a、L(i)>=L(2i) 且 L(i)>=L(2i +1) (1<=i<=n/2) ,则为大根堆
b、L(i)<=L(2i),且L(i)<=L(wi+1)(1<=i<=n/2), 则为小根堆
算法思想:
外部排序
数据太多,无法全部存放在内存中