鸡毛插龟壳 基数冒泡插入归并 稳定
目录
一、插入排序
1.直接插入排序
稳定性
带哨兵和不带哨兵没什么区别,带哨兵就是不用单开变量,把数组第一个位置空出来
时间复杂度
最好:O(n) 【有序情况】
最坏:O() 【从大到小】
#include<bits/stdc++.h>
using namespace std;
void insertsort(int a[],int n)
{
int i,j,t;
for(i=0;i<n;i++)
{
if(a[i-1]>a[i])
{
t=a[i];
for(j=i;a[j-1]>t&&j>=0;j--)
a[j]=a[j-1];
a[j]=t;
}
}
}
int main()
{
int a[10]={6,5,4,9,3,2,1};
insertsort(a,7);
for(int i=0;i<7;i++)
cout<<a[i]<<" ";
return 0;
}
2.折半插入排序
稳定
先回顾一下二分查找,只适用有序的顺序表。
首先二分查找的时间复杂度:O(logn)
时间复杂度
最好:O(n) 【有序情况】
最坏:O() 【从大到小】
#include<bits/stdc++.h>
using namespace std;
void b_insertsort(int a[],int n)
{
int i,j,t;
for(i=0; i<n; i++)
{
int low=0,high=i-1,mid;
t=a[i];
while(low<=high)
{
mid=(low+high)/2;
if(t<a[mid]) high=mid-1;
else low=mid+1;
}
for(j=i; j>low; j--)
a[j]=a[j-1];
a[low]=t;
}
}
int main()
{
int a[10]= {6,5,4,9,3,2,1};
b_insertsort(a,7);
for(int i=0; i<7; i++)
cout<<a[i]<<" ";
return 0;
}
3.希尔排序
不稳定
直接插入排序算法的时间复杂度为O() ,但若待排序列为正序,其时间复杂度可提高至O(n) ,可见其适用于基本 有序的排序表和数据量不大的排序表,希尔排序基于此对直接插入排序进行改进,又称为缩小增量排序。
希尔排序的基本思想是:先将待排序表分割成若干特殊子表,即把相隔某个增量的记录组成一个子表,对各子表分别进行直接插入排序,当整个表中的元素“基本有序”时,再对全体记录进行一 次直接插入排序。
希尔排序的时间复杂度依赖于步长的函数,其时间复杂度分析困难
#include<bits/stdc++.h>
using namespace std;
void shell(int a[],int n)
{
int i,j,t;
for(int dk=n/2;dk>=1;dk/=2)
{
for(i=dk;i<n;i++)
{
if(a[i-dk]>a[i])
{
t=a[i];
for(j=i;j>=dk&&a[j-dk]>t;j-=dk)
{
a[j]=a[j-dk];
}
a[j]=t;
}
}
}
}
int main()
{
int a[10]= {6,5,4,9,3,2,1};
shell(a,7);
for(int i=0; i<7; i++)
cout<<a[i]<<" ";
return 0;
}
二、交换排序
1.冒泡排序
时间复杂度
最好:O(n) 【有序情况】
最坏:O() 【从大到小】
key:两两比较;
每一轮比较都能确定当前最大数的位置
void popsort(int a[],int n)
{
for(int i=0;i<n-1;i++)
{
for(int j=0;j<n-1-i;j++)
{
if(a[j+1]<a[j])
{
int t=a[j+1];
a[j+1]=a[j];
a[j]=t;
}
}
}
}
#include<bits/stdc++.h>
using namespace std;
void popsort(int a[],int n)
{
for(int i=0; i<n-1; i++)
{
//int f=0;
for(int j=0; j<n-1-i; j++)
{
if(a[j]>a[j+1])
{
int t=a[j];
a[j]=a[j+1];
a[j+1]=t;
//f=1;
}
}
//if(f==0)
//break;
}
}
int main()
{
int a[10]= {6,5,4,9,3,2,1};
popsort(a,7);
for(int i=0; i<7; i++)
cout<<a[i]<<" ";
return 0;
}
2.快速排序(重要!!!)
快速排序算法基于分治思想,在待排序表中任取一个元素作为基准,通过一趟排序将待排序表划分为独立的两部分,使得左边的元素小于基准元素,右边的元素大于基准元素,基准元素最终放在中间,然后分别递归得对两个子表重复 上述过程,直至每部分内只有一个元素或空为止,即所有元素放在了其最终位置上。
时间复杂度
最好:O(nlogn) 【有序情况】
最坏:O() 【从大到小】
划分分治
不稳定
思考,快速排序如何从不稳定变成稳定?
让每个数不一样即可,考虑用二元组,<ai,i>来唯一标识。
【常规代码】
#include<bits/stdc++.h>
using namespace std;
int part(int a[],int low,int high)
{
int key=a[low];
while(low<high)
{
while(low<high&&a[high]>=key) high--;
a[low]=a[high];
while(low<high&&a[low]<key) low++;
a[high]=a[low];
}
a[low]=key;
return low;
}
void quick_sort(int a[],int low,int high)
{
if(low<high)
{
int pos=part(a,low,high);
quick_sort(a,low,pos-1);
quick_sort(a,pos+1,high);
}
}
int main()
{
int a[10]= {6,5,4,9,3,2,1};
quick_sort(a,0,6);
for(int i=0; i<7; i++)
cout<<a[i]<<" ";
return 0;
}
注意:在划分最后别忘了return位置
这里应用到了递归的知识注意理解,可以想象一棵二叉树配合思考
【优化代码】
void quick_sort(int q[], int l, int r)
{
if (l >= r) return;
int i = l - 1, j = r + 1, x = q[l + r >> 1];
while (i < j)
{
do i ++ ; while (q[i] < x);
do j -- ; while (q[j] > x);
if (i < j) swap(q[i], q[j]);
}
quick_sort(q, l, j), quick_sort(q, j + 1, r);
}
while (i < j)
:只要i
小于j
,继续循环。do i ++ ; while (q[i] < x);
:i
从左向右扫描,找到第一个不小于基准值x
的元素。do j -- ; while (q[j] > x);
:j
从右向左扫描,找到第一个不大于基准值x
的元素。if (i < j) swap(q[i], q[j]);
:如果i
仍小于j
,交换q[i]
和q[j]
,将小于x
的元素移动到左侧,大于x
的元素移动到右侧。
三、选择排序
1.简单选择排序
每次都找最小的放到前面的队列
时间复杂度
最好:O()
最坏:O()
// 交换a和b的值
void swap(int &a, int &b){
int temp = a;
a = b;
b = temp;
}
// 对A[]数组共n个元素进行选择排序
void SelectSort(int A[], int n)
{
//一共进行n-1趟,i指向待排序序列中第一个元素
for(int i=0; i<n-1; i++)
{
int min = i;
for(int j=i+1; j<n; j++){ //在A[i...n-1]中选择最小的元素
if(A[j]<A[min])
min = j;
}
if(min!=i)
swap(A[i], A[min]);
}
}
2.堆排序
堆排序:不稳定
大根堆(完全二叉树)cf 二叉搜索树
空间复杂度:O(1)
建立大根堆代码 时间复杂度O(n)
步骤:
1.构建最大堆:从数组的最后一个非叶子节点开始,依次向上调整每个节点,使整个数组成为一个最大堆,即父节点的值大于等于其子节点的值。
2.堆排序: 将堆顶元素(即最大值)与堆的最后一个元素交换,这样最大值就被固定在数组的最后。 将堆的大小减一,并对新的堆顶元素进行堆调整,使剩余部分重新成为最大堆。 重复上述过程,直到堆的大小为1。
正序:大根堆
逆序:小根堆
// 对初始序列建立大根堆
void BuildMaxHeap(int A[], int len){
for(int i=len/2; i>0; i--) //从后往前调整所有非终端结点
HeadAdjust(A, i, len);
}
// 将以k为根的子树调整为大根堆
void HeadAdjust(int A[], int k, int len){
A[0] = A[k];
for(int i=2*k; i<=len; i*=2){ //沿k较大的子结点向下调整
if(i<len && A[i]<A[i+1])
i++;
if(A[0] >= A[i])
break;
else{
A[k] = A[i]; //将A[i]调整至双亲结点上
k=i; //修改k值,以便继续向下筛选
}
}
A[k] = A[0]
}
四、归并排序(重要!!!)
有序的合并,二路归并
分治思想
时间复杂度
最好:O(nlogn)
最坏:O(nlogn)
#include<bits/stdc++.h>
using namespace std;
int tmp[10];
void merge_sort(int a[], int low, int high) {
if (low >= high) return; // 基本情况:子数组中只有一个元素或为空,直接返回
int mid = low + high >> 1; // 计算中间位置
merge_sort(a, low, mid); // 递归排序左半部分
merge_sort(a, mid + 1, high); // 递归排序右半部分
// 合并两个已经排好序的子数组
int k = 0, i = low, j = mid + 1;
while (i <= mid && j <= high)
if (a[i] <= a[j]) tmp[k ++ ] = a[i ++ ];
else tmp[k ++ ] = a[j ++ ];
while (i <= mid) tmp[k ++ ] = a[i ++ ];
while (j <= high) tmp[k ++ ] = a[j ++ ];
//将临时数组 tmp[] 中的元素依次复制到原数组 a[] 的从 low 到 high 的位置上。
for (i = low, j = 0; i <= high; i ++, j ++ ) a[i] = tmp[j];
}
int main()
{
int a[10]= {6,5,4,9,3,2,1};
merge_sort(a,0,6);
for(int i=0; i<7; i++)
cout<<a[i]<<" ";
return 0;
}
五、其他
1.基数排序
基数排序基于关键字各位的大小进行排序。这个基数也就是数学中各种进制数字表示法的基数,由于我们用的是十进制,在此处,基数就是10。这个算法分配十个桶,标号0到9,先按照个位数入桶,然后从左到右遍历桶,将元素取出形成一个序列,再按照十位数字入桶,以此类推。所有关键字位都比较完毕后,排序结束,得到有序的排序结果。分析一下这个操作,就知道,按照个位数入桶的时候,个位数小的第二轮会先入桶,也就是说,第二轮完毕之后,个桶内十位数相同,但是个位数小的被压在下面。继续分析,到百位数入桶的时候,每个桶十位数小的被压在下面,那这些被压在下边的十位数相同的一组,他们的个位数是否有序呢?显然是有的,其大小方向,取决于我们上一层的桶的取出方式为保持小的压在下面的性质,出桶应该采用先进先出的方式。
面试问:为什么先个位
个人思考:首先每次都需要按位重排。最高位次起到决定性作用,放在最后,最后一次排序即根据最高位。
2.外部排序
408数据结构学习笔记——外部排序_考研外部排序笔记-CSDN博客
部分图片资料来源:王道