版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34719188/article/details/83931519
排序算法
在计算器科学与数学中,一个排序算法(英语:Sorting algorithm)是一种能将一串数据依照特定排序方式进行排列的一种算法。最常用到的排序方式是数值顺序以及字典顺序。有效的排序算法在一些算法(例如搜索算法与合并算法)中是重要的,如此这些算法才能得到正确解答。排序算法也用在处理文字数据以及产生人类可读的输出结果。基本上,排序算法的输出必须遵守下列两个原则:
- 输出结果为递增序列(递增是针对所需的排序顺序而言)
- 输出结果是原输入的一种排列、或是重组
虽然排序算法是一个简单的问题,但是从计算器科学发展以来,在此问题上已经有大量的研究。举例而言,冒泡排序在1956年就已经被研究。虽然大部分人认为这是一个已经被解决的问题,有用的新算法仍在不断的被发明。(例子:图书馆排序在2004年被发表) ------[维基百科]
排序算法一览
项目 | 内容 |
---|---|
理论储备 | • 计算复杂性理论 • 大O符号 • 全序关系 • 列表 • 稳定性 • 比较排序 • 自适应排序 • 排序网络 • 整数排序 |
交换排序 | • 冒泡排序 • 鸡尾酒排序 • 奇偶排序 • 梳排序 • 侏儒排序 • 快速排序 • 臭皮匠排序 • Bogo排序 |
选择排序 | • 选择排序 • 堆排序 • 平滑排序 • 笛卡尔树排序 • 锦标赛排序 • 圈排序 |
插入排序 | • 插入排序 • 希尔排序 • 伸展排序 • 二叉查找树排序 • 图书馆排序 • 耐心排序 |
归并排序 | • 归并排序 • 梯级归并排序 • 振荡归并排序 • 多相归并排序 • 列表排序 |
分布排序 | • 美国旗帜排序 • 珠排序 • 桶排序 • 爆炸排序 • 计数排序 • 比较计数排序 • 鸽巢排序 • 相邻图排序 • 基数排序 • 闪电排序 • 插值排序 |
并发排序 | • 双调排序器 • Batcher归并网络 • 两两排序网络 |
混合排序 | • 块排序 • Tim排序 • 内省排序 • Spread排序 • J排序 |
其他 | • 拓扑排序 • 煎饼排序 • 意粉排序 |
稳定性
当相等的元素是无法分辨的,比如像是整数,稳定性并不是一个问题。
然而,假设以下的数对将要以他们的第一个数字来排序。
(4,1)(3,1)(3,7)(5,6)
在这个状况下,有可能产生两种不同的结果,一个是让相等键值的纪录维持相对的次序,而另外一个则没有:
(3,1)(3,7)(4,1)(5,6) ------ 维持次序
(3,7)(3,1)(4,1)(5,6) ------ 次序改变
扫描二维码关注公众号,回复:
4017902 查看本文章
不稳定排序算法可能会在相等的键值中改变纪录的相对次序,但是稳定排序算法从来不会如此。
稳定的排序
- 冒泡排序(bubble sort)— O(n2)
- 插入排序(insertion sort)— O(n2)
- 鸡尾酒排序(cocktail sort)— O(n2)
- 桶排序(bucket sort)— O(n);需要 O(k) 额外空间
- 计数排序(counting sort)— O(n+k);需要 O(n+k)额外空间
- 归并排序(merge sort)— O(n log n);需要 O(n)额外空间
- 原地归并排序 — O(nlog2n)
- 二叉排序树排序(binary tree sort)— O(n log n)期望时间;O(n2)最坏时间;需要 O(n)额外空间
- 鸽巢排序(pigeonhole sort)— O(n+k);需要 O(k) 额外空间
- 基数排序(radix sort)— O(nk);需要 O(n)额外空间
- 侏儒排序(gnome sort)— O(n2)
- 图书馆排序(library sort)— O(n log n)期望时间;O(n2)最坏时间;需要 (1+ε)n额外空间
- 块排序(block sort)— O(n log n)
不稳定的排序
- 选择排序(selection sort)— O(n2)
- 希尔排序(shell sort)— O(n log2n)
- C - lover排序算法(Clover sort)— O(n)期望时间, O(n2)最坏情况
- 梳排序 — O(n log n)
- 堆排序(heap sort)— O(n log n)
- 平滑排序(smooth sort)— O(n log n)
- 快速排序(quick sort)— O(n log n)期望时间, O(n2)最坏情况;对于大的、随机数列表一般相信是最快的已知排序
- 内省排序(introsort)— O(n log n)
- 耐心排序(patience sort)— O(n log n+k) 最坏情况时间, O(n+k)空间,也需要找到最长的递增子序列(longest increasing subsequence)
不实用的排序
- Bogo排序 — O(n * n!),最坏的情况下期望时间为无穷。
- Stupid排序 — O(n3);递归版本需要O(n2)额外存储器
- 珠排序(bead sort)— O(n) 或 O(sqrt(n)),但需要特别的硬件
- 煎饼排序 — O(n),但需要特别的硬件
- 臭皮匠排序(stooge sort)算法简单,但需要约 n2.7 的时间
常用排序算法比较
算法实现
插入排序
// 一般方法
void insertSort(int array[], int arraySize) {
int i, j;
int tmp;
for(i = 1; i < arraySize; i++) {
tmp = array[i];
j = i - 1;
while(j >= 0 && tmp > array[j]) { /*从大到小排序,因此改变判断条件*/
array[j + 1] = array[j--];
}
array[j + 1] = tmp; /*将元素tmp插入指定位置*/
}
}
// 模板方法
template<typename T>
void insertionSort(T arr[], int n){
for( int i = 1 ; i < n ; i ++ ) {
// 寻找元素arr[i]合适的插入位置
// 写法1
// for( int j = i ; j > 0 ; j-- )
// if( arr[j] < arr[j-1] )
// swap( arr[j] , arr[j-1] );
// else
// break;
// 写法2
// for( int j = i ; j > 0 && arr[j] < arr[j-1] ; j -- )
// swap( arr[j] , arr[j-1] );
// 写法3
T e = arr[i];
int j; // j保存元素e应该插入的位置
for (j = i; j > 0 && arr[j-1] > e; j--)
arr[j] = arr[j-1];
arr[j] = e;
}
return;
}
冒泡排序
// 一般方法
void bubbleSort(int array[],int arraySize)
{
int i,j,tmp ,flag = 1;
for(i=0;i<arraySize-1 && flag == 1;i++){ /*arraySize个元素的序列执行arraySize-1趟冒泡排序*/
flag = 0; /*flag初始化为0*/
for(j=0;j<arraySize-i;j++) {
if(array[j]<array[j+1]) { /*数据交换,将较小的数据往后交换,实现从大到小排序*/
tmp = array[j+1];
array[j+1] = array[j];
array[j] = tmp;
flag = 1; /*发生数据交换,标志flag置为1*/
}
}
}
}
// 模板方法
template<typename T>
void bubbleSort( T arr[] , int n){
bool swapped;
//int newn; // 理论上,可以使用newn进行优化,但实际优化效果较差
do{
swapped = false;
//newn = 0;
for( int i = 1 ; i < n ; i ++ )
if( arr[i-1] > arr[i] ){
swap( arr[i-1] , arr[i] );
swapped = true;
// 可以记录最后一次的交换位置,在此之后的元素在下一轮扫描中均不考虑
// 实际优化效果较差,因为引入了newn这个新的变量
//newn = n;
}
//n = newn;
// 优化,每一趟Bubble Sort都将最大的元素放在了最后的位置
// 所以下一次排序,最后的元素可以不再考虑
// 理论上,newn的优化是这个优化的复杂版本,应该更有效
// 实测,使用这种简单优化,时间性能更好
n --;
}while(swapped);
}
选择排序
// 一般方法
void selectSort(int array[], int arraySize) {
int i, j, min, tmp;
for(i = 0; i < arraySize - 1; i++) {
min = i;
for(j = i + 1; j < arraySize; j++) { /*在未排序的子序列中找到最小的元素位置*/
if(array[j] < array[min]) {
min = j; /*用min记录下最小元素的位置*/
}
}
if(min != i) { /*最小的元素不位于子序列的第1个位置*/
tmp = array[min] ;
array[min] = array[i]; /*元素的交换*/
array[i] = tmp;
}
}
}
// 模板方法
template<typename T>
void selectionSort(T arr[], int n){
for(int i = 0 ; i < n ; i ++){
int minIndex = i;
for( int j = i + 1 ; j < n ; j ++ )
if( arr[j] < arr[minIndex] )
minIndex = j;
swap( arr[i] , arr[minIndex] );
}
}
希尔排序
// 一般方法
void shellSort(int array[],int arraySize)
{
int i, j, flag ,gap = arraySize;
int tmp;
while(gap > 1){
gap = gap/2; /*按照经验值,每次缩小增量一半*/
do{ /*子序列可以使用冒泡排序*/
flag = 0;
for(i=0;i<arraySize-gap;i++){
j = i + gap;
if(array[i]>array[j]) { /*子序列按照冒泡排序方法处理*/
tmp = array[i]; /*交换元素位置*/
array[i] = array[j];
array[j] = tmp;
flag = 1; /*设置标志flag*/
}
}
}while(flag !=0); /*改进了的冒泡排序法*/
}
}
// 模板方法
template<typename T>
void shellSort(T arr[], int n){
// 计算 increment sequence: 1, 4, 13, 40, 121, 364, 1093...
int h = 1;
while( h < n/3 )
h = 3 * h + 1;
while( h >= 1 ){
// h-sort the array
for( int i = h ; i < n ; i ++ ){
// 对 arr[i], arr[i-h], arr[i-2*h], arr[i-3*h]... 使用插入排序
T e = arr[i];
int j;
for( j = i ; j >= h && e < arr[j-h] ; j -= h )
arr[j] = arr[j-h];
arr[j] = e;
}
h /= 3;
}
}
归并排序
// 使用优化的归并排序算法, 对arr[l...r]的范围进行排序
template<typename T>
void __mergeSort2(T arr[], int l, int r){
// 优化2: 对于小规模数组, 使用插入排序
if( r - l <= 15 ){
insertionSort(arr, l, r);
return;
}
int mid = (l+r)/2;
__mergeSort2(arr, l, mid);
__mergeSort2(arr, mid+1, r);
// 优化1: 对于arr[mid] <= arr[mid+1]的情况,不进行merge
// 对于近乎有序的数组非常有效,但是对于一般情况,有一定的性能损失
if( arr[mid] > arr[mid+1] )
__merge(arr, l, mid, r);
}
template<typename T>
void mergeSort2(T arr[], int n){
__mergeSort2( arr , 0 , n-1 );
}
快速排序
方法一
void swap(int *a, int *b) {
/*交换序列中元素的位置*/
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
}
void quickSortArray(int array[], int s, int t) {
int low, high;
if(s < t) {
low = s;
high = t + 1;
while(1) {
do low++;
while(array[low] >= array[s] && low != t); /*array[s]为基准元素,重复执行low++操作*/
do high--;
while(array[high] <= array[s] && high != s); /*array[s]为基准元素,重复执行high--操作*/
if(low < high)
swap(&array[low], &array[high]); /*交换array[low]和array[high]的位置*/
else
break;
}
swap(&array[s], &array[high]); /*将基准元素与array[high]进行交换*/
quickSortArray (array, s, high - 1); /*将基准元素前面的子序列快速排序*/
quickSortArray (array, high + 1, t); /*将基准元素后面的子序列快速排序*/
}
}
void quickSort(int array[], int arraySize) {
quickSortArray(array, 0, arraySize - 1);
}
方法二
// 对arr[l...r]部分进行partition操作
// 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
template <typename T>
int _partition(T arr[], int l, int r){
// 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
swap( arr[l] , arr[rand()%(r-l+1)+l] );
T v = arr[l];
int j = l;
for( int i = l + 1 ; i <= r ; i ++ )
if( arr[i] < v ){
j ++;
swap( arr[j] , arr[i] );
}
swap( arr[l] , arr[j]);
return j;
}
// 双路快速排序的partition
// 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
template <typename T>
int _partition2(T arr[], int l, int r){
// 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
swap( arr[l] , arr[rand()%(r-l+1)+l] );
T v = arr[l];
// arr[l+1...i) <= v; arr(j...r] >= v
int i = l+1, j = r;
while( true ){
// 注意这里的边界, arr[i] < v, 不能是arr[i] <= v
// 思考一下为什么?
while( i <= r && arr[i] < v )
i ++;
// 注意这里的边界, arr[j] > v, 不能是arr[j] >= v
// 思考一下为什么?
while( j >= l+1 && arr[j] > v )
j --;
// 对于上面的两个边界的设定, 有的同学在课程的问答区有很好的回答:)
// 大家可以参考: http://coding.imooc.com/learn/questiondetail/4920.html
if( i > j )
break;
swap( arr[i] , arr[j] );
i ++;
j --;
}
swap( arr[l] , arr[j]);
return j;
}
// 对arr[l...r]部分进行快速排序
template <typename T>
void _quickSort(T arr[], int l, int r){
// 对于小规模数组, 使用插入排序进行优化
if( r - l <= 15 ){
insertionSort(arr,l,r);
return;
}
// 调用双路快速排序的partition
int p = _partition2(arr, l, r);
_quickSort(arr, l, p-1 );
_quickSort(arr, p+1, r);
}
template <typename T>
void quickSort(T arr[], int n){
srand(time(NULL));
_quickSort(arr, 0, n-1);
}
三路快速排序
// 递归的三路快速排序算法
template <typename T>
void __quickSort3Ways(T arr[], int l, int r){
// 对于小规模数组, 使用插入排序进行优化
if( r - l <= 15 ){
insertionSort(arr,l,r);
return;
}
// 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
swap( arr[l], arr[rand()%(r-l+1)+l ] );
T v = arr[l];
int lt = l; // arr[l+1...lt] < v
int gt = r + 1; // arr[gt...r] > v
int i = l+1; // arr[lt+1...i) == v
while( i < gt ){
if( arr[i] < v ){
swap( arr[i], arr[lt+1]);
i ++;
lt ++;
}
else if( arr[i] > v ){
swap( arr[i], arr[gt-1]);
gt --;
}
else{ // arr[i] == v
i ++;
}
}
swap( arr[l] , arr[lt] );
__quickSort3Ways(arr, l, lt-1);
__quickSort3Ways(arr, gt, r);
}
template <typename T>
void quickSort3Ways(T arr[], int n){
srand(time(NULL));
__quickSort3Ways( arr, 0, n-1);
}
堆排序
方法一
#include "stdio.h"
void adjust(int k[],int i,int n)
{
int j;
int tmp;
tmp = k[i-1];
j = 2 * i ;
while(j<=n)
{
if(j<n && k[j-1]>k[j])
{
j++; /* j为i的左右孩子中较小孩子的序号*/
}
if(tmp<=k[j-1])
{
break; /*tmp为最小的元素,则不需要元素的交换*/
}
k[j/2-1] = k[j-1]; /*交换元素位置*/
j = 2 * j ;
}
k[j/2-1] = tmp;
}
void heapSort(int k[],int n)
{
int i,j;
int tmp;
for(i=n/2;i>=1;i--)
{
adjust(k,i,n); /*将原序列初始化成一个小顶堆*/
}
for(i=n-1;i>=0;i--)
{
tmp = k[i]; /*调整序列元素*/
k[i] = k[0];
k[0] = tmp;
adjust(k,1,i);
}
}
main()
{
int i,k[10]= {5,2,12,6,9,0,3,6,15,20};
printf("The orginal data array is\n") ;
for(i=0;i<10;i++) /*显示原序列之中的元素*/
printf("%d ",k[i]);
heapSort(k,10); /*快速排序*/
printf("\nThe result of heap sorting for the array is\n");
for(i=0;i<10;i++) /*显示排序后的结果*/
printf("%d ",k[i]);
getchar();
getchar();
}
方法二
template<typename Item>
class MaxHeap{
private:
Item *data;
int count;
int capacity;
void shiftUp(int k){
while( k > 1 && data[k/2] < data[k] ){
swap( data[k/2], data[k] );
k /= 2;
}
}
void shiftDown(int k){
while( 2*k <= count ){
int j = 2*k;
if( j+1 <= count && data[j+1] > data[j] ) j ++;
if( data[k] >= data[j] ) break;
swap( data[k] , data[j] );
k = j;
}
}
public:
// 构造函数, 构造一个空堆, 可容纳capacity个元素
MaxHeap(int capacity){
data = new Item[capacity+1];
count = 0;
this->capacity = capacity;
}
// 构造函数, 通过一个给定数组创建一个最大堆
// 该构造堆的过程, 时间复杂度为O(n)
MaxHeap(Item arr[], int n){
data = new Item[n+1];
capacity = n;
for( int i = 0 ; i < n ; i ++ )
data[i+1] = arr[i];
count = n;
for( int i = count/2 ; i >= 1 ; i -- )
shiftDown(i);
}
~MaxHeap(){
delete[] data;
}
// 返回堆中的元素个数
int size(){
return count;
}
// 返回一个布尔值, 表示堆中是否为空
bool isEmpty(){
return count == 0;
}
// 像最大堆中插入一个新的元素 item
void insert(Item item){
assert( count + 1 <= capacity );
data[count+1] = item;
shiftUp(count+1);
count ++;
}
// 从最大堆中取出堆顶元素, 即堆中所存储的最大数据
Item extractMax(){
assert( count > 0 );
Item ret = data[1];
swap( data[1] , data[count] );
count --;
shiftDown(1);
return ret;
}
// 获取最大堆中的堆顶元素
Item getMax(){
assert( count > 0 );
return data[1];
}
};
// heapSort1, 将所有的元素依次添加到堆中, 在将所有元素从堆中依次取出来, 即完成了排序
// 无论是创建堆的过程, 还是从堆中依次取出元素的过程, 时间复杂度均为O(nlogn)
// 整个堆排序的整体时间复杂度为O(nlogn)
template<typename T>
void heapSort1(T arr[], int n){
MaxHeap<T> maxheap = MaxHeap<T>(n);
for( int i = 0 ; i < n ; i ++ )
maxheap.insert(arr[i]);
for( int i = n-1 ; i >= 0 ; i-- )
arr[i] = maxheap.extractMax();
}
// heapSort2, 借助我们的heapify过程创建堆
// 此时, 创建堆的过程时间复杂度为O(n), 将所有元素依次从堆中取出来, 实践复杂度为O(nlogn)
// 堆排序的总体时间复杂度依然是O(nlogn), 但是比上述heapSort1性能更优, 因为创建堆的性能更优
template<typename T>
void heapSort2(T arr[], int n){
MaxHeap<T> maxheap = MaxHeap<T>(arr,n);
for( int i = n-1 ; i >= 0 ; i-- )
arr[i] = maxheap.extractMax();
}