浅析十大常见排序(含C++代码)

时间:2022-10-06 10:37:17

首先声明一下,本文只对十种排序算法做简单总结,并参照一些资料给出自己的代码实现,并没有对某种算法理论讲解,更详细的

了解可以参考以下资料:

1、《data structure and algorithm analysis in c 》

2、《大话数据结构》

3、http://blog.csdn.net/morewindows/article/details/7961256

4、http://www.cs.usfca.edu/~galles/visualization/Algorithms.html

5、https://www.runoob.com/w3cnote/ten-sorting-algorithm.html(导航页,详细分析十大排序,使用多种语言)

一、冒泡排序

基本思想是:两两比较相邻记录的关键字,如果反序则交换

冒泡排序时间复杂度最好的情况为O(n),最坏的情况是O(n^2)

改进思路1:设置标志位,明显如果有一趟没有发生交换(flag = false),说明排序已经完成

改进思路2:记录一轮下来标记的最后位置,下次从头部遍历到这个位置就Ok

二、直接插入排序

将一个记录插入到已经排好序的有序表中, 从而得到一个新的,记录数增1的有序表 

时间复杂度也为O(n^2), 比冒泡法和选择排序的性能要更好一些

 

三、简单选择排序

通过n-i次关键字之间的比较,从n-i+1 个记录中选择关键字最小的记录,并和第i(1<=i<=n)个记录交换之

尽管与冒泡排序同为O(n^2),但简单选择排序的性能要略优于冒泡排序

四、希尔排序

先将整个待排元素序列分割成若干子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排

,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序(增量为1)。其时间复杂度为O(n^3/2),要好于直接

插入排序的O(n^2)

五、归并排序

假设初始序列含有n个记录,则可以看成n个有序的子序列,每个子序列的长度为1,然后两两归并,得到(不小于n/2的最小整数)个长度为2

或1的有序子序列,再两两归并,...如此重复,直至得到一个长度为n的有序序列为止,这种排序方法称为2路归并排序。 时间复杂度为

O(nlogn),空间复杂度为O(n+logn),如果非递归实现归并,则避免了递归时深度为logn的栈空间 空间复杂度为O(n)

 

六、堆排序

堆是具有下列性质的完全二叉树:每个节点的值都大于或等于其左右孩子节点的值,称为大顶堆;或者每个节点的值都小于或等于其左

右孩子节点的值,称为小顶堆。

堆排序就是利用堆进行排序的方法.基本思想是:将待排序的序列构造成一个大顶堆.此时,整个序列的最大值就是堆顶 的根结点.将它移

走(其实就是将其与堆数组的末尾元素交换, 此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元

素的次大值.如此反复执行,便能得到一个有序序列了。 时间复杂度为 O(nlogn),好于冒泡,简单选择,直接插入的O(n^2)

七、快速排序

通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。时间复杂度为O(nlogn)

下文没有给出快速排序的实现,参考以前的文章

浅析十大常见排序(含C++代码)

代码实现:(含3种swap交换函数,6个排序算法,不含快速排序)

 C++ Code 

#include<iostream>
using namespace std; void swap1(int *left, int *right)
{
int temp = *left;
*left = *right;
*right = temp;
} void swap2(int &left, int &right)
{
int temp = left;
left = right;
right = left;
} void swap3(int &left, int &right)
{
if (&left != &right)
{
left ^= right;
right ^= left;
left ^= right;
}
} /*****************************************************************/
/* 冒泡排序时间复杂度最好的情况为O(n),最坏的情况是O(n^2)
* 基本思想是:两两比较相邻记录的关键字,如果反序则交换 */ void BubbleSort1(int arr[], int num)
{
int i, j;
for (i = ; i < num; i++)
{
for (j = ; j < num - i; j++)
{
if (arr[j - ] > arr[j])
swap1(&arr[j - ], &arr[j]);
}
}
} // 改进思路:设置标志位,明显如果有一趟没有发生交换(flag = flase),说明排序已经完成.
void BubbleSort2(int arr[], int num)
{
int k = num;
int j;
bool flag = true;
while (flag)
{
flag = false;
for (j = ; j < k; j++)
{
if (arr[j - ] > arr[j])
{
swap1(&arr[j - ], &arr[j]);
flag = true;
}
}
k--;
}
}
//改进思路:记录一轮下来标记的最后位置,下次从头部遍历到这个位置就Ok
void BubbleSort3(int arr[], int num)
{
int k, j;
int flag = num;
while (flag > )
{
k = flag;
flag = ;
for (j = ; j < k; j++)
{
if (arr[j - ] > arr[j])
{
swap1(&arr[j - ], &arr[j]);
flag = j;
}
}
}
}
/*************************************************************************/ /**************************************************************************/
/*插入排序: 将一个记录插入到已经排好序的有序表中, 从而得到一个新的,记录数增1的有序表
* 时间复杂度也为O(n^2), 比冒泡法和选择排序的性能要更好一些 */ void InsertionSort(int arr[], int num)
{
int temp;
int i, j;
for (i = ; i < num; i++)
{
temp = arr[i];
for (j = i; j > && arr[j - ] > temp; j--)
arr[j] = arr[j - ];
arr[j] = temp;
}
} /****************************************************************************/ /*希尔排序:先将整个待排元素序列分割成若干子序列(由相隔某个“增量”的元素组成的)分别进行
直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,
再对全体元素进行一次直接插入排序(增量为1)。其时间复杂度为O(n^3/2),要好于直接插入排序的O(n^2) */
void ShellSort(int *arr, int N)
{
int i, j, increment;
int tmp;
for (increment = N / ; increment > ; increment /= )
{
for (i = increment; i < N; i++)
{
tmp = arr[i];
for (j = i; j >= increment; j -= increment)
{
if (arr[j - increment] > tmp)
arr[j] = arr[j - increment];
else
break;
}
arr[j] = tmp;
} }
} /**************************************************************************/ /* 简单选择排序(simple selection sort) 就是通过n-i次关键字之间的比较,从n-i+1
* 个记录中选择关键字最小的记录,并和第i(1<=i<=n)个记录交换之
* 尽管与冒泡排序同为O(n^2),但简单选择排序的性能要略优于冒泡排序 */ void SelectSort(int arr[], int num)
{
int i, j, Mindex;
for (i = ; i < num; i++)
{
Mindex = i;
for (j = i + ; j < num; j++)
{
if (arr[j] < arr[Mindex])
Mindex = j;
} swap1(&arr[i], &arr[Mindex]);
}
} /********************************************************************************/
/*假设初始序列含有n个记录,则可以看成n个有序的子序列,每个子序列的长度为1,然后
* 两两归并,得到(不小于n/2的最小整数)个长度为2或1的有序子序列,再两两归并,...
* 如此重复,直至得到一个长度为n的有序序列为止,这种排序方法称为2路归并排序
* 时间复杂度为O(nlogn),空间复杂度为O(n+logn),如果非递归实现归并,则避免了递归时深度为logn的栈空间
* 空间复杂度为O(n) */ /*lpos is the start of left half, rpos is the start of right half*/
void merge(int a[], int tmp_array[], int lpos, int rpos, int rightn)
{
int i, leftn, num_elements, tmpos; leftn = rpos - ;
tmpos = lpos;
num_elements = rightn - lpos + ; /*main loop*/
while (lpos <= leftn && rpos <= rightn)
if (a[lpos] <= a[rpos])
tmp_array[tmpos++] = a[lpos++];
else
tmp_array[tmpos++] = a[rpos++]; while (lpos <= leftn) /*copy rest of the first part*/
tmp_array[tmpos++] = a[lpos++];
while (rpos <= rightn) /*copy rest of the second part*/
tmp_array[tmpos++] = a[rpos++]; /*copy array back*/
for (i = ; i < num_elements; i++, rightn--)
a[rightn] = tmp_array[rightn];
} void msort(int a[], int tmp_array[], int left, int right)
{
int center; if (left < right)
{
center = (right + left) / ;
msort(a, tmp_array, left, center);
msort(a, tmp_array, center + , right);
merge(a, tmp_array, left, center + , right);
}
} void merge_sort(int a[], int n)
{
int *tmp_array;
tmp_array = (int *)malloc(n * sizeof(int)); if (tmp_array != NULL)
{
msort(a, tmp_array, , n - );
free(tmp_array);
} else
printf("No space for tmp array!\n");
} /************************************************************************************/
/* 堆是具有下列性质的完全二叉树:每个节点的值都大于或等于其左右孩子节点的值,称为大顶堆;
* 或者每个节点的值都小于或等于其左右孩子节点的值,称为小顶堆*/ /*堆排序就是利用堆进行排序的方法.基本思想是:将待排序的序列构造成一个大顶堆.此时,整个序列的最大值就是堆顶
* 的根结点.将它移走(其实就是将其与堆数组的末尾元素交换, 此时末尾元素就是最大值),然后将剩余的n-1个序列重新
* 构造成一个堆,这样就会得到n个元素的次大值.如此反复执行,便能得到一个有序序列了
*/
/* 时间复杂度为 O(nlogn),好于冒泡,简单选择,直接插入的O(n^2) */ // 构造大顶堆
#define leftChild(i) (2*(i) + 1) void percDown(int *arr, int i, int N)
{
int tmp, child;
for (tmp = arr[i]; leftChild(i) < N; i = child)
{
child = leftChild(i);
if (child != N - && arr[child + ] > arr[child])
child++;
if (arr[child] > tmp)
arr[i] = arr[child];
else
break;
}
arr[i] = tmp;
} void HeapSort(int *arr, int N)
{
int i;
for (i = N / ; i >= ; i--)
percDown(arr, i, N);
for (i = N - ; i > ; i--)
{
swap1(&arr[], &arr[i]);
percDown(arr, , i);
}
} int main(void)
{
int arr[] = { , , , , , , , , , };
HeapSort(arr, );
for (int i = ; i < ; i++) cout << arr[i] << ' '; cout << endl; return ; }

注:上述7种都是比较排序,下面3种都是非比较排序,理论上可以达到O(n),比比较排序要快,但是这3种都是有其应用背景才能发挥作用的,否则适得其反。

八:计数排序

计数排序(Counting sort)是一种稳定的排序算法。计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。

算法的步骤如下:

  1. 找出待排序的数组中最大和最小的元素
  2. 统计数组中每个值为i的元素出现的次数,存入数组C的第i
  3. 对所有的计数累加(从C中的位置为1的元素开始,每一项和前一项相加)
  4. 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。

九:桶排序

桶排序 (Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)

桶排序以下列程序进行:

  1. 设置一个定量的数组当作空桶子。
  2. 寻访串行,并且把项目一个一个放到对应的桶子去。(hash)
  3. 对每个不是空的桶子进行排序。
  4. 从不是空的桶子里把项目再放回原来的串行中。

十:基数排序

基数排序(英语:Radix sort)是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。

它是这样实现的:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。

 C++ Code 
#include<stdio.h>
#include<string.h>
#include<algorithm> using namespace std; /*****************计数排序*******************************/
void CountSort(int *arr, int num)
{
int mindata = arr[];
int maxdata = arr[];
for (int i = ; i < num; i++)
{
if (arr[i] > maxdata)
maxdata = arr[i];
if (arr[i] < mindata)
mindata = arr[i];
} int size = maxdata - mindata + ;
//申请空间并初始化为0
int *pCount = (int *)malloc(sizeof(int) * size);
memset(pCount, , sizeof(int)*size); //记录排序计数,每出现一次在对应位置加1
for (int i = ; i < num; i++)
++pCount[arr[i]-mindata]; //确定不比该位置大的数据个数
for (int i = ; i < size; i++)
pCount[i] += pCount[i - ]; //加上前一个的计数 int *pSort = (int *)malloc(sizeof(int) * num);
memset((char*)pSort, , sizeof(int) * num); //从末尾开始拷贝是为了重复数据首先出现的排在前面,即稳定排序
for (int i = num - ; i >= ; i--)
{
//包含自己需要减1,重复数据循环回来也需要减1
--pCount[arr[i]-mindata];
pSort[pCount[arr[i]-mindata]] = arr[i];
}
//拷贝到原数组
for (int i = ; i < num; i++)
arr[i] = pSort[i]; free(pCount);
free(pSort); } /*****************桶排序*****************************/
struct Node
{
int key_;
struct Node *next_;
Node(int key)
{
key_ = key;
next_ = NULL;
}
}; #define bucket_size 10 //与数组元素个数相等 void buck_sort(int arr[], int num)
{
Node *bucket_table[bucket_size];
memset(bucket_table, , sizeof(bucket_table)); //建立每一个头节点,头节点的key保存当前桶的数据量
for (int i = ; i < bucket_size; i++)
bucket_table[i] = new Node(); int maxValue = arr[];
for (int i = ; i < num; i++)
{
if (arr[i] > maxValue)
maxValue = arr[i];
} for (int j = ; j < num; j++)
{
Node *ptr = new Node(arr[j]);//其余节点的key保存数据 //映射函数计算桶号
// index = (value * number_of_elements) / (maxvalue + 1)
int index = (arr[j] * bucket_size) / (maxValue + );
Node *head = bucket_table[index];
//该桶还没有数据
if (head->key_ == )
{
bucket_table[index]->next_ = ptr;
(bucket_table[index]->key_)++; }
else
{
//找到合适的位置插入
while (head->next_ != NULL && head->next_->key_ <= ptr->key_)
head = head->next_;
ptr->next_ = head->next_;
head->next_ = ptr;
(bucket_table[index]->key_)++;
} } //将桶中的数据拷贝回原数组
int m, n;
for (m = , n = ; n < num && m < bucket_size; m++, n++)
{
Node *ptr = bucket_table[m]->next_;
while (ptr != NULL)
{
arr[n] = ptr->key_;
ptr = ptr->next_;
n++;
}
n--;
} //释放分配的动态空间
for (m = ; m < bucket_size; m++)
{
Node *ptr = bucket_table[m];
Node *tmp = NULL;
while (ptr != NULL)
{
tmp = ptr->next_;
delete ptr;
ptr = tmp;
} } } /****************************************************/ /******************** 基数排序LSD*********************/ void base_sort_ISD(int *arr, int num)
{
Node *buck[]; // 创建一个链表数组
Node *tail[]; //保存每条链表尾节点指针集合,
//这样插入buck数组时就不用每次遍历到末尾
int i, MaxValue, kth, high, low;
Node *ptr;
for(MaxValue = arr[], i = ; i < num; i++)
MaxValue = max(MaxValue, arr[i]); memset(buck, , sizeof(buck));
memset(tail, , sizeof(buck)); for(low = ; high = low * , low < MaxValue; low *= )
{
//只要没排好序就一直排序
for(i = ; i < num; i++)
{
//往桶里放
kth = (arr[i] % high) / low;//取出数据的某一位,作为桶的索引
ptr = new Node(arr[i]); //创建新节点 //接到末尾
if (buck[kth] != NULL)
{
tail[kth]->next_ = ptr;
tail[kth] = ptr;
}
else
{
buck[kth] = ptr;
tail[kth] = ptr;
} }
//把桶中的数据放回数组中(同条链表是从头到尾)
for (kth = , i = ; kth < num; i++)
{
while (buck[i] != NULL)
{
arr[kth++] = buck[i]->key_;
ptr = buck[i];
buck[i] = buck[i]->next_;
delete ptr;
}
} memset(tail, , sizeof(buck)); } }
/**************************************************************/ int main(void)
{ int arr1[] = {, , , , , , , , , };
int size1 = sizeof(arr1) / sizeof(arr1[]);
CountSort(arr1, size1);
for (int i = ; i < size1; i++)
printf("%d ", arr1[i]);
printf("\n"); int arr2[] = {, , , , , , , , , };
int size2 = sizeof(arr2) / sizeof(arr2[]);
  base_sort_ISD(arr2, size2);   for (int i = ; i < size2; i++)
    printf("%d ", arr2[i]);
  printf("\n");   int arr3[] = {, , , , , , , , , };   int size3 = sizeof(arr3) / sizeof(arr3[]);
  buck_sort(arr3, size3);   for (int i = ; i < size3; i++)
    printf("%d ", arr3[i]);
  printf("\n");   return ;
}