插入排序
基本思想:每次将一个待排序的记录按其关键字大小插入前面已经排好序的子序列,直到全部记录插入完成。
直接插入排序
#include<bits/stdc++.h>
using namespace std;
int A[1000];//定义数组存储数据元素
void DirectOrder(int A[],int n){
for(int i=2;i<=n;i++){//从第二个元素到第n个元素依次插入
if(A[i]<A[i-1]){//如果该元素小于其前驱元素
A[0]=A[i];//将其复制到A[0]位置上
int j;
for(j=i-1;A[0]<A[j];j--){//将前面已经排好序的元素依次往后移动,直到找到插入的位置
A[j+1]=A[j];
}
A[j+1]=A[0];//当不满足A[0]<A[j]时,说明该元素已经找到了位置,即A[j+1]
}
}
}
int main(){
int n;//数据总数
scanf("%d",&n);
for(int i=1;i<=n;i++){
cin>>A[i];//输入数据,从A[1]开始存储
}
DirectOrder(A,n);
for(int i=1;i<=n;i++){//输出结果
cout<<A[i]<<" ";
}
return 0;
}
折半插入排序
与直接插入排序的区别:直接插入边比较边移动,而折半插入先找出待插入的位置,后统一移动元素。折半插入排序仅减少了比较次数,而移动次数不变。
#include<bits/stdc++.h>
using namespace std;
int A[1000];//定义数组存储数据元素
void DirectOrder(int A[],int n){
int i,j,low,high,mid;
for(int i=2;i<=n;i++){//依次将A[2]-A[n]插入到前面的已排序序列
A[0]=A[i];//将A[i]暂存到A[0]中
low=1;high=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];//插入到high+1的位置
}
}
int main(){
int n;//数据总数
scanf("%d",&n);
for(int i=1;i<=n;i++){
cin>>A[i];//输入数据,从A[1]开始存储
}
DirectOrder(A,n);
for(int i=1;i<=n;i++){//输出结果
cout<<A[i]<<" ";
}
return 0;
}
希尔排序
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int A[N];//定义数组存储数据元素
void ShellOrder(int A[],int n){
int dk,i,j;
for(dk=n/2;dk>=1;dk=dk/2){//增量变化
for(i=dk+1;i<=n;i++){//从某个增量下的子序列中的第二个元素开始进行直接插入排序
if(A[i]<A[i-dk]){//如果该元素小于其在子序列中的前驱元素
A[0]=A[i];//将其复制到A[0]位置上
for(j=i-dk;j>0&&A[0]<A[j];j-=dk){//将子序列中前面已经排好序的元素依次往后移动dk个位置,直到找到插入的位置 !!!注意j>0的条件,否则AC不通过:数组越界
A[j+dk]=A[j];
}
A[j+dk]=A[0];//当不满足A[0]<A[j]时,说明该元素已经找到了位置,即A[j+dk]
}
}
}
}
int main(){
int n;//数据总数
cin>>n;
for(int i=1;i<=n;i++){
cin>>A[i];//输入数据,从A[1]开始存储
}
ShellOrder(A,n);
for(int i=1;i<=n;i++){//输出结果
cout<<A[i]<<" ";
}
return 0;
}
交换排序
基本思想:根据序列中两个元素关键字的比较结果来对换这两个记录在序列中的位置。
冒泡排序
#include<bits/stdc++.h>
using namespace std;
int A[1000];//定义数组存储数据元素
void BubbleOrder(int A[],int n){
for(int i=0;i<n-1;i++){//要排这么多趟 :最多是n-1趟
bool flag=false;//一趟下来有没有发生交换
for(int j=n;j>0;j--){
if(A[j-1]>A[j]){//如果是逆序,则交换
swap(A[j-1],A[j]);
flag=true;//标志置为true
}
}
if(flag==false) return;
}
}
int main(){
int n;//数据总数
scanf("%d",&n);
for(int i=1;i<=n;i++){
cin>>A[i];//输入数据,从A[1]开始存储
}
BubbleOrder(A,n);
for(int i=1;i<=n;i++){//输出结果
cout<<A[i]<<" ";
}
return 0;
}
快速排序
#include<bits/stdc++.h>
using namespace std;
int A[1000];//定义数组存储数据元素
int Partition(int A[],int low,int high){
int pivot=A[low];//指定第一个元素为基准
while(low<high){
while(low<high&&A[high]>=pivot) high--;//注意pivot是元素,不是下标
A[low]=A[high];//将比基准小的移到左端
while(low<high&&A[low]<=pivot) low++;
A[high]=A[low];//将比基准大的移动到右端
}
A[low]=pivot;//基准放在最终位置
return low;//返回最终位置
}
void QuickSort(int A[],int low,int high){
if(low>=high) return;//递归跳出条件
int pivotpos=Partition(A,low,high);
QuickSort(A,low,pivotpos-1);
QuickSort(A,pivotpos+1,high);
}
int main(){
int n;//数据总数
scanf("%d",&n);
for(int i=1;i<=n;i++){
cin>>A[i];//输入数据,从A[1]开始存储
}
QuickSort(A,1,n);
for(int i=1;i<=n;i++){//输出结果
cout<<A[i]<<" ";
}
return 0;
}
选择排序
基本思想:每一趟(如第i趟)在后面n-i+1(i=1,2,...,n-1)个待排序元素中选取关键字最小的元素,作为有序子序列的第i个元素,直到第n-1趟做完,待排序元素只剩下1个,就不用再选择。
简单选择排序
#include<bits/stdc++.h>
using namespace std;
int A[1000];//定义数组存储数据元素
void SelectSort(int A[],int n){
for(int i=1;i<n;i++){//一共需要排n-1趟
int min=i;//初始化最小元素的位置
for(int j=i+1;j<=n;j++){//从下一个元素开始依次比较,找到本趟的最小元素
if(A[min]>A[j]) min=j;//更新最小元素的位置
}
if(min!=i) swap(A[min],A[i]);//如果第i趟的最小元素不在第i个元素,则把它放在第i个位置上
}
}
int main(){
int n;//数据总数
scanf("%d",&n);
for(int i=1;i<=n;i++){
cin>>A[i];//输入数据,从A[1]开始存储
}
SelectSort(A,n);
for(int i=1;i<=n;i++){//输出结果
cout<<A[i]<<" ";
}
return 0;
}
堆排序
该算法较难理解,可结合算法可视化来帮助理解算法步骤 ,推荐一个算法可视化网站:Data Structure Visualization (lerogo.com)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int A[N];
void HeapAdjust(int A[],int k,int len){//len是元素的总个数
//对以元素k为根的子树进行调整
A[0]=A[k];//A[0]暂存子树的根结点
for(int i=2*k;i<=len;i*=2){//注意是<=len
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];//被筛选结点的值放入最终位置
}
void BuidHeap(int A[],int len){
for(int i=len/2;i>0;i--){//从i=[n/2]~1,反复调整堆
HeapAdjust(A,i,len);
}
}
void HeapSort(int A[],int len){
BuidHeap(A,len);//初始建堆
for(int i=len;i>1;i--){//n-1趟的交换和建堆过程
swap(A[i],A[1]);//输出堆顶元素(和堆底元素交换)
HeapAdjust(A,1,i-1);//把剩余的i-1个元素整理成堆
}
}
int main(){
int n;//数据总数
scanf("%d",&n);
for(int i=1;i<=n;i++){
cin>>A[i];//输入数据,从A[1]开始存储
}
HeapSort(A,n);
for(int i=1;i<=n;i++){//输出结果
cout<<A[i]<<" ";
}
return 0;
}
归并排序
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int A[N];
void Merge(int A[],int low,int mid,int high){
int B[N];//定义辅助数组B
int i,j,k;
for(k=low;k<=high;k++){//将A中的元素全部复制到B中
B[k]=A[k];
}
for(i=low,j=mid+1,k=i;i<=mid&&j<=high;k++){//!!!注意这个循环条件的写法:参数初始化;循环条件;自增量
if(B[i]<B[j]) A[k]=B[i++];//将左右序列比较中较小的那个元素赋值到A数组中
else A[k]=B[j++];
}
while(i<=mid) A[k++]=B[i++];//当左序列有剩余元素,直接将其复制到A数组中
while(j<=high) A[k++]=B[j++];//右序列同理
}
void MergeSort(int A[],int low,int high){
if(low<high){//递归终止条件
int mid=(low+high)/2;//!!!注意中间元素的计算
MergeSort(A,low,mid);//递归左序列排序
MergeSort(A,mid+1,high);//递归有序列排序
Merge(A,low,mid,high);
}
}
int main(){
int n;//数据总数
scanf("%d",&n);
for(int i=1;i<=n;i++){
cin>>A[i];//输入数据,从A[1]开始存储
}
MergeSort(A,1,n);
for(int i=1;i<=n;i++){//输出结果
cout<<A[i]<<" ";
}
return 0;
}
基数排序
#include <iostream>
#include <queue>
using namespace std;
queue<int> q[10];
int a[100005];
void Distribute(int a[], int n, int b)
{ // b 代表按照数字第几位进行排序
int mod = 1;
while (--b)
mod *= 10;
for (int i = 0; i < n; i++) //根据位数分配元素
q[a[i] / mod % 10].push(a[i]);
}
void Collect(int a[])
{
int len = 0;
for (int i = 0; i < 10; i++)
{
while (!q[i].empty())
{
a[len++] = q[i].front(); //收集元素
q[i].pop();
}
}
}
int main(void)
{
int n, amax = -1e9;
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
scanf("%d", &a[i]);
amax = max(a[i], amax); //找出序列中的最大值,确定数字的最大位数 b
}
int b = 1;
while (amax)
{
Distribute(a, n, b);
Collect(a);
amax /= 10;
++b;
}
for (int i = 0; i < n; i++)
printf("%d ", a[i]);
printf("\n");
}
各个算法性质总结
时间、空间复杂度比较
排序算法的使用范围:
1、直接插入排序、折半插入排序、冒泡排序和简单选择排序主要用于元素个数n不是很大(n<10000)的情形;
2、对于中等规模的元素序列(n<=1000)希尔排序是一种非常好的选择;
3、对于元素个数n很大的情况下,可以采用快排、堆排序、归并排序或基数排序;
4、在初始序列基本有序的情况下(除去n个元素中的k个元素后即呈有序,k<<n),排序效率最高的算法是直接插入排序
5、快速排序在要排序的数基本有序的情况下最不利于发挥其长处
6、适合并行处理的排序算法是快速排序
比较次数、排序趟数与序列初始状态是否有关:
比较次数无关的有:选择排序、2路归并排序(多路归并有关)、基数排序
比较次数有关的有:直接插入排序、冒泡排序、堆排序、希尔排序、快速排序
排序趟数无关的有:直接插入排序、折半插入排序、希尔排序、简单选择排序、归并排序、基数排序
排序趟数有关的有:冒泡排序、快速排序(树高h)
一趟排序之后元素是否放在了它的最终位置上:
堆排序,简单选择排序,冒泡排序、快速排序
顺序存储和链式存储对算法的影响:
希尔排序和堆排序若使用链式存储,算法性能会降低
关键字的类型的影响:
基数排序不能对float和double类型的实数进行排序
移动次数与关键字初始排列次序无关的是:
基数排序