大部分内容来自于“一像素”以及其他的网上信息。待完善(关于不同版本代码的实现以及应用实例)
目录:
一、常见算法分类
二、算法复杂度
三、相关概念
四、算法详情以及代码实现
一、 常见的排序算法可分为两类
非线性时间比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此称为非线性时间比较类排序。
线性时间非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序。
二、 复杂度比较
三、 相关概念
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数
四、 算法详情--非线性时间比较算法
交换排序: 冒泡排序 与 快速排序
4.1 冒泡排序
定义(描述):
它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
算法:
- 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
- 针对所有的元素重复以上的步骤,除了最后一个;
- 重复步骤1~3,直到排序完成。
动画演示:
代码实现(待完善):
C++版本
//冒泡排序
void bubbleSort(int arr[], int n)
{
for(int i = 0;i < n;i++){
//比较两个相邻的元素
for(int j = 0;j < n-i-1;j++){
if(arr[j] > arr[j+1]){
int t = arr[j];
arr[j] = arr[j+1];
arr[j+1] = t;
}
}
}
}
Python版本
MATLAB 版本
4.2 快速排序
定义:
其思想是:先选一个“标尺”,用它把整个队列过一遍筛子, 以保证,其左边的元素都不大于它,其右边的元素都不小于它。这样,排序问题就被分割为两个子区间,再分别对子区间排序就可以了。
算法:
快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
从数列中挑出一个元素,称为 “基准”(pivot);
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
动画演示:
代码实现:
C++版本
//快速排序
void quickSort(int *arr,int l,int r)
{
//此处编写代码实现快速排序
int i,j,x,temp;
if(l<r)
{
i=l;
j=r;
x=arr[(l+r)/2];
//以中间元素为轴
while(1)
{
while(i<=r&&arr[i]<x)
i++;
while(j>=0&&arr[j]>x)
j--;
if(i>=j) //相遇则跳出
break;
else
{
temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
//交换
}
}
quickSort(arr,l,i-1); //对左半部分进行快排
quickSort(arr,j+1,r); //对右半部分进行快排
}
}
插入排序:简单插入 与 希尔排序
4.3 简单插入排序
定义:
插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
算法描述:
- 从第一个元素开始,该元素可以认为已经被排序;
- 取出下一个元素,在已经排序的元素序列中从后向前扫描;
- 如果该元素(已排序)大于新元素,将该元素移到下一位置;
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
- 将新元素插入到该位置后;
- 重复步骤2~5。
动画演示:
代码实现:
C++版本
//插入排序
void insertSort(int arr[], int n){
for(int i = 1;i < n;i++){
int temp = arr[i];
int j = i - 1;
while(temp < arr[j]){
arr[j+1] = arr[j];
j--;
if(j == -1){
break;
}
}
arr[j+1] = temp;
}
}
4.4 希尔排序
定义:
1959年Shell发明,第一个突破O(n2)的排序算法,是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。
算法描述:
选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
按增量序列个数k,对序列进行k 趟排序;
每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
动画演示:
代码实现:
C++版本
// 希尔排序
void Shellsort(int a[], int n) {
int i, j, gap;
for (gap = n / 2; gap > 0; gap /= 2)
{
//for (i = 0; i < gap; i++)
for (i = gap; i < n; i++)
{
for (j = i - gap; j >= 0; j -= gap)
{
if (a[j + gap] < a[j])
swap(a[j + gap], a[j]);
}
}
}
}
选择排序:简单选择 与 堆排序
4.5 简单选择排序
定义:
选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
算法描述:
- 初始状态:无序区为R[1..n],有序区为空;
- 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R[i+1..n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
- n-1趟结束,数组有序化了。
动画演示:
代码实现:
C++版本
//选择排序
void choiceSort(int arr[], int n){
for(int i = 0;i < n; i++){
int m = i;
for(int j = i + 1;j < n;j++){
//如果第j个元素比第m个元素小,将j赋值给m
if(arr[j] < arr[m]){
m = j;
}
}
//交换m和i两个元素的位置
if(i != m){
int t = arr[i];
arr[i] = arr[m];
arr[m] = t;
}
}
}
4.6 堆排序
定义:
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
算法描述:
- 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
- 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
- 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
动画演示:
代码实现:
C++版本
//堆排序
void Heapify(int arr[], int first, int end){
int father = first;
int son = father * 2 + 1;
while(son < end){
if(son + 1 < end && arr[son] < arr[son+1]) ++son;
//如果父节点大于子节点则表示调整完毕
if(arr[father] > arr[son]) break;
else {
//不然就交换父节点和子节点的元素
int temp = arr[father];
arr[father] = arr[son];
arr[son] = temp;
//父和子节点变成下一个要比较的位置
father = son;
son = 2 * father + 1;
}
}
}
void HeapSort(int arr[],int len){
int i;
//初始化堆,从最后一个父节点开始
for(i = len/2 - 1; i >= 0; --i){
Heapify(arr,i,len);
}
//从堆中的取出最大的元素再调整堆
for(i = len - 1;i > 0;--i){
int temp = arr[i];
arr[i] = arr[0];
arr[0] = temp;
//调整成堆
Heapify(arr,0,i);
}
}
归并排序:二归并排序 与 多路排序
4.7 二归并排序
定义:
归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
算法简述:
- 把长度为n的输入序列分成两个长度为n/2的子序列;
- 对这两个子序列分别采用归并排序;
- 将两个排序好的子序列合并成一个最终的排序序列。
动画演示:
代码实现:
C++版本
void MergeArray(int a[], int first, int mid, int last, int tmp[]){
int i = first, n = mid;
int j = mid + 1, m = last;
int k = 0;
while (i <= n&&j <= m)
{
if (a[i] < a[j])
tmp[k++] = a[i++];
else
tmp[k++] = a[j++];
}
while (i <= n) {
tmp[k++] = a[i++];
}
while (j <= m)
tmp[k++] = a[j++];
for (i = 0; i < k; i++)
a[first + i] = tmp[i];
}
// 先递归地分解数列,再合并数列完成归并排序
void MergesortSection(int a[], int first, int last, int tmp[]){
if (first < last)
{
int mid = (first + last) / 2;
MergesortSection(a, first, mid, tmp); // 左边有序
MergesortSection(a, mid + 1, last, tmp); // 右边有序
MergeArray(a, first, mid, last, tmp); // 合并两个有序序列
}
}
void Mergesort(int a[], int n){
int *p = new int[n];
if (p == NULL)
return;
MergesortSection(a, 0, n - 1, p);
delete[] p;
}
4.8 多路归并排序
定义:
代码实现:
异同:
四、 算法详情--线性时间非比较算法
4.9 基数排序
定义:
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。
算法描述:
- 取得数组中的最大数,并取得位数;
- arr为原始数组,从最低位开始取每个位组成radix数组;
- 对radix进行计数排序(利用计数排序适用于小范围数的特点);
动画演示:
代码实现:
C++
//基数排序
//寻找数组中最大数的位数作为基数排序循环次数
int KeySize(int a[], int n){
int key = 1;
for(int i=0;i<n;i++){
int temp = 1;
int r = 10;
while(a[i]/r>0){
temp++;
r*=10;
}
key = (temp>key)?temp:key;
}
return key;
}
//基数排序
void RadixSort(int a[], int n){
int key = KeySize(a,n);
int bucket[10][10]={0};
int order[10]={0};
for(int r = 1;key>0;key--,r*=10){
for(int i=0;i<n;i++){
int lsd = (a[i]/r)%10;
bucket[lsd][order[lsd]++]=a[i];
}
int k = 0;
for(int i = 0;i<10;i++){
if(order[i]!=0){
for(int j = 0;j<order[i];j++)
a[k++]=bucket[i][j];
}
order[i]=0;
}
}
}
异同:
4.10 桶排序
定义:
桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。
算法描述:
-
- 设置一个定量的数组当作空桶;
- 遍历输入数据,并且把数据一个一个放到对应的桶里去;
- 对每个不是空的桶进行排序;
- 从不是空的桶里把排好序的数据拼接起来。
动画演示:
代码实现:
C++版本
//桶排序 1,桶排序是稳定的 2,桶排序是常见排序里最快的一种,比快排还要快…大多数情况下 3,桶排序非常快,但是同时也非常耗空间,基本上是最耗空间的一种排序算法
void BucketSort(int a[],int n)
{
int maxVal = a[0]; //假设最大为arr[0]
for(int x = 1; x < n; x++) //遍历比较,找到大的就赋值给maxVal
{
if(a[x] > maxVal)
maxVal = a[x];
}
int tmpArrLen = maxVal + 1;
int *tmpArr = new int[tmpArrLen]; //获得空桶大小
int i, j;
for( i = 0; i < tmpArrLen; i++) //空桶初始化
tmpArr[i] = 0;
for(i = 0; i < n; i++) //寻访序列,并且把项目一个一个放到对应的桶子去。
tmpArr[ a[i] ]++;
for(i = 0, j = 0; i < tmpArrLen; i++)
{
while( tmpArr[ i ] != 0) //对每个不是空的桶子进行排序。
{
a[j ] = i; //从不是空的桶子里把项目再放回原来的序列中。i为索引,数组的索引位置就表示值
j++;
tmpArr[i]--;
}
}
}
异同:
4.11 计数排序
定义:
对于每个元素x,找出比x小的数的个数,从而确定x在排好序的数组中的位置。此算法需要辅助数组,是以空间换时间。
算法描述:
- 找出待排序的数组中最大和最小的元素;
- 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
- 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
- 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。
动画演示:
代码实现:
C++版本
void CountSort(vector<int> &arr, int maxVal) {
int len = arr.size();
if (len < 1)
return;
vector<int> count(maxVal+1, 0);
vector<int> tmp(arr);
for (auto x : arr)
count[x]++;
for (int i = 1; i <= maxVal; ++i)
count[i] += count[i - 1];
for (int i = len - 1; i >= 0; --i) {
arr[count[tmp[i]] - 1] = tmp[i];
count[tmp[i]]--; //注意这里要减1
}
}
int main()
{
int i;
int a[] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 10};
// 计数排序 OK
int max = a[0];
for (int i = 1;i < 10; ++i)
{
if (a[i]>max)
{
max = a[i];
}
}
Counting_sort(a,10,max+1);
for (int i = 0; i < 10; ++i)
{
cout<<a[i]<<" ";
}
system("pause");
return 0;
}
1、基于比较的排序算法有:
(1)直接插入排序;
(2)冒泡排序;
(3)简单选择排序;
(4)希尔排序;
(5)快速排序;
(6)堆排序;
(7)归并排序。
2、基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部分资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog(r)m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。
Reference
1.https://www.cnblogs.com/onepixel/articles/7674659.html
2.http://www.ganecheng.tech/blog/52652705.html