在清理瑞格题目的时候有感而发,借鉴了很多大佬的算法总结和代码,最后留下来以备之后复习用。
——2020.12.31
【序】排序概述
排序,顾名思义,就是把一列杂乱无序的数重新排列成有序的数列,通常是线性表中的操作。
通过排序时的不同思想,可以把排序分成以下几种类型:
【排序的稳定性】若a1=a2,排序前a1在a2前面,排完序之后a1仍在a2前面,那么称该排序法为稳定排序。否则称为不稳定排序。
稳定的排序算法:冒泡排序、直接插入排序、归并排序。
不稳定的排序算法:堆排序、快速排序、希尔排序、简单选择排序。
下面利用瑞格的题目要求给出各种排序算法的代码:
1.冒泡排序
比较相邻的两两元素,如果反序则交换,直到没有反序元素为止。每次循环都能确定一个最大元或最小元,故称为冒泡排序。
冒泡排序最好情况下时间复杂度为O(n),最坏情况下时间复杂度为O(n2)。
瑞格8546:
#include <bits/stdc++.h>
using namespace std;
void bubblesort(int a[],int n)
{
for(int i=0;i<n;i++){
for(int j=0;j<n-i;j++){
if(a[j]>a[j+1]){
int tmp=a[j];
a[j]=a[j+1];
a[j+1]=tmp;
}
}
}
}
int main()
{
int a[100],n=0,x;
while(cin>>x && x!=0){
a[n]=x;
n++;
}
bubblesort(a,n-1);
for(int i=0;i<n;i++)
cout<<a[i]<<" ";
return 0;
}
2.简单选择排序
简单选择排序就是每次遍历寻找最大或最小的元素,然后让该元素与他该在的位置上的元素交换。
时间复杂度仍为O(n2),但由于交换元素的次数少,故算法性能要比冒泡略好一些。
瑞格8548:
#include <bits/stdc++.h>
using namespace std;
void selectsort(int a[],int n)
{
for(int i=0;i<n;i++){
int k=i;
for(int j=i;j<=n;j++){
if(a[j]<a[k])
k=j;
}
int tmp=a[i];
a[i]=a[k];
a[k]=tmp;
}
}
int main()
{
int a[100],n=0,x;
while(cin>>x && x!=0){
a[n]=x;
n++;
}
selectsort(a,n-1);
for(int i=0;i<n;i++)
cout<<a[i]<<" ";
return 0;
}
3.直接插入排序
直接插入排序就是每次遍历,然后找到这个元素在这个串中的合适位置,直到全部插完为止。
直接插入排序最好情况下时间复杂度也为O(n),最坏情况下时间复杂度为O(n2),它在性能上比起冒泡排序和简单选择排序要好一些。
瑞格8545:
#include <bits/stdc++.h>
using namespace std;
void insertsort(int a[],int n)
{
for(int i=2;i<=n;i++){
int tmp=a[i],j=i;
while(j>1 && tmp<a[j-1]){
a[j]=a[j-1];
j--;
}
a[j]=tmp;
}
}
int main()
{
int a[100],n=1,x;
while(cin>>x && x!=0){
a[n]=x;
n++;
}
insertsort(a,n-1);
for(int i=1;i<n;i++)
cout<<a[i]<<" ";
return 0;
}
注:以上三种算法的时间复杂度均为O(n2),下面几种算法都是上面三种算法的改进版,让时间复杂度突破了O(n2)的界限。
4.希尔排序
希尔排序的思想是按照增量进行分组,然后在各组之内使用直接插入排序。
希尔排序的关键是增量的选取,但目前还没有一种最好的增量选法,故每次折半即可。
希尔排序的时间复杂度是O(n3/2),突破了O(n2)的慢速排序界限,更加高效了,但它却是不稳定的排序。
瑞格8547:
#include <bits/stdc++.h>
using namespace std;
void shellsort(int a[],int n)
{
for(int gap=n/2;gap>0;gap/=2){
for(int i=gap;i<n;i++){
int j=i;
int tmp=a[j];
if(a[j]<a[j-gap]){
while(j-gap>=0 && tmp<a[j-gap]){
a[j]=a[j-gap];
j-=gap;
}
a[j]=tmp;
}
}
}
}
int main()
{
int a[100],n=0,x;
while(cin>>x && x!=0){
a[n]=x;
n++;
}
shellsort(a,n);
for(int i=0;i<n;i++)
cout<<a[i]<<" ";
return 0;
}
5.堆排序
堆排序是一种利用堆这种数据结构进行的排序。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
在堆的数据结构中,堆中的最大值总是位于根节点(在优先队列中使用堆的话堆中的最小值位于根节点)。堆中定义以下几种操作:
最大堆调整(Max Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点
创建最大堆(Build Max Heap):将堆中的所有数据重新排序
堆排序(HeapSort):移除位在第一个数据的根节点,并做最大堆调整的递归运算
堆排序不适合排序序列个数较少的情况,堆排序算法的时间复杂度是O(nlogn),但是它是一种不稳定排序。
瑞格8550:
#include <iostream>
#include <vector>
using namespace std;
//交换两个数
void swap(vector<int> &arr,int i,int j)
{
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
//再次调整为大顶堆
void heapify(vector<int> &arr,int index,int heapsize)
{
int left = index*2 + 1;
while(left<heapsize)
{
int largest = left+1<heapsize && arr[left+1] > arr[left] ? left+1 : left;
largest = arr[largest] > arr[index] ? largest : index;
if (index == largest)
break;
swap(arr,index,largest);
index = largest;
left = index*2 + 1;
}
}
//变为大顶堆
void heapinsert(vector<int> &arr,int index)
{
while(arr[index] > arr[(index-1)/2])
{
swap(arr,index,(index-1)/2);
index = (index-1)/2;
}
}
//排序
void heapsort(vector<int> &arr)
{
if (arr.size()<2)
return;
for (int i=0;i<arr.size();i++)
heapinsert(arr,i);
int heapsize = arr.size();
swap(arr,0,--heapsize);
while(heapsize>0)
{
heapify(arr,0,heapsize);
swap(arr,0,--heapsize);
}
}
int main()
{
vector<int> arr;
int n,temp;
while(cin>>temp && temp!=0)
arr.push_back(temp);
heapsort(arr);
for(int i=0;i<arr.size();i++)
cout<<arr[i]<<" ";
return 0;
}
6.快速排序
思想:分治法,通过一趟排序将待排记录分割成独立的两个部分,其中一部分的关键字均比另一部分记录的关键字小,再对这两部分继续进行递归排序,直到各部分中只有一个数。
排序方法:
快速排序算法通过多次比较和交换来实现排序,其排序流程如下:
(1)首先设定一个分界值,通过该分界值将数组分成左右两部分。
(2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。
(3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
(4)重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。
快速排序不适合排序序列个数较少的情况,它是目前综合性能最佳的排序算法,快速排序算法的时间复杂度是O(nlogn),但是它是一种不稳定排序。
#include <bits/stdc++.h>
using namespace std;
int p[100];
void quick_sort(int p[], int l, int r)
{
if (l>=r) return;
int x=p[(l+r+1)/2],i=l-1,j=r+1;
while (i<j)
{
do i++; while (p[i]<x);
do j--; while (p[j]>x);
if (i<j) swap(p[i],p[j]);
}
quick_sort(p,l,i-1);
quick_sort(p,i,r);
}
int main()
{
int cnt=0,x;
while(cin>>x && x!=0){
p[cnt]=x;
cnt++;
}
quick_sort(p,0,cnt-1);
for(int i=0;i<cnt;i++)
printf("%d ",p[i]);
}
7.归并排序
归并排序就是利用了“归并”思想的排序方法,这里只介绍最基本的2-路归并排序,2-路归并排序的原理是,将序列两两分组,将序列归并为⌈n/2⌉个组,在这个组内单独排序;然后将这些组再两两归并,生成⌈n/4⌉个组,组内再单独排序,依此类推,直到只剩下一个组为止,归并排序的时间复杂度为O(nlogn).
#include <bits/stdc++.h>
using namespace std;
const int maxn=100;
//将数组a的[l1,r1]与[l2,r2]区间合并为有序区间
void merge(int a[],int l1,int r1,int l2,int r2)
{
int i=l1,j=l2;
int tmp[maxn],index=0;
while(i<=r1 && j<=r2){
if(a[i]<=a[j])
tmp[index++]=a[i++];
else
tmp[index++]=a[j++];
}
while(i<=r1)
tmp[index++]=a[i++];
while(j<=r2)
tmp[index++]=a[j++];
for(i=0;i<index;i++)
a[l1+i]=tmp[i];
}
//将数组a当前区间[l,r]进行归并排序
void mergesort(int a[],int l,int r)
{
if(l<r){
int mid=(l+r)/2;
mergesort(a,l,mid);
mergesort(a,mid+1,r);
merge(a,l,mid,mid+1,r);
}
}
int main()
{
int a[maxn],n;
cin>>n;
for(int i=0;i<n;i++)
cin>>a[i];
mergesort(a,0,n);
for(int i=0;i<n;i++)
cout<<a[i]<<" ";
return 0;
}
排序算法其实远不止于此,还有很多其他的算法。在排序这么一件简简单单的事情上,也会有很多新的发现。之后还有其他算法的情况下,还会回来补充的。
这篇博客算是跨年作了,年末动笔,年初完工,结了个尾,也开了个头,在接下来的一年里,我也会继续精进,继续加油。
——2021.1.1
更新添加了归并排序
——2021.1.13