常见经典排序算法

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了常见经典排序算法相关的知识,希望对你有一定的参考价值。

插入排序:
算法简介:接插入排序(Insertion Sort)的基本思想是:每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子序列中的适当位置,直到全部记录插入完成为止。时间复杂度为O(n^2)。 最稳定的排序算法但是效率很低
代码实现:
void InsertSort(int *arr,int n)
{
                 for (int index = 0; index < n-1; ++index)
                {
                                 int end = index+1;
                                 int tmp = arr [end];
                                 while (end>0&&tmp<arr [end - 1])
                                {
                                                 arr[end] = arr [end - 1];
                                                end--;
                                }
                                 arr[end] = tmp;
                }
}
 
显然当最小的数字在数组的最右端时,此时需要将整个数组进行移位,效率很低,而希尔排序可以有效的改善这个问题
希尔排序:希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法
       先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上比前两种方法有较大提高。
代码实现:
void ShellSort(int *arr, int n)//希尔排序
{
                 int gap = n ;
                 while (gap>1)//由于gap=gap/3+1 最小值为1 则在gap=1时跳出循环
                {
                                gap = gap / 3 + 1; //{ 2, 8, 9, 6, 1, 3, 4, 5, 7, 0 ,-1,-2}//注意这里的+1  当gap=1时此时排序等同于插入排序 但是由于之前将最小的数据已经移到最左边所以效率
//高于插入排序
                                 for (int index = 0; index <n-gap; ++index)
                                {
                                                 int end = index;
                                                 int tmp = arr [end+gap];
                                                 while (end>= 0 && arr [end]>tmp)                                                {
                                                                 arr[end+gap] = arr [end];
                                                                end -=gap;//此时插入间距为end
                                                }
                                                 arr[end+gap] = tmp;
                                }
                }
}
附注:上面希尔排序的步长选择都是从n/3+1开始,每次再取上一次的三分之一加1,直到最后为1。关于希尔排序步长参见维基百科:http://zh.wikipedia.org/wiki/%E5%B8%8C%E5%B0%94%E6%8E%92%E5%BA%8F
冒泡排序:20世纪经典算法之一,
原理是临近的数字两两进行比较,按照从小到大或者从大到小的顺序进行交换,这样一趟过去后,最大或最小的数字被交换到了最后一位,再进行第二趟冒泡,由此我们可以写出以下代码:
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])
                                                                swap( arr[j], arr [j + 1]);
                                }
                }
}
这里我们设立一个标记变量 flag用来标记数列中的数是否在循环结束前就已经排好序
代码改进如下:
void BubbleSort(int *arr, int n)
{

                 bool flag = true ;
                 int k = n ;
                 while (flag)
                {
                                flag = false;
                                 for (int i = 1; i < k; ++i)
                                {
                                                 if (arr [i - 1]<arr[i])
                                                {
                                                                swap( arr[i - 1], arr [i]);
                                                                flag = true;//有发生交换 继续冒泡 否则说明已经有序
                                                }

                                }
                                k--;
                }

}
    如果这一趟发生了交换,这flag为rrue,则还需要继续进行冒泡,否则说明数组已经有序,后面的不必进行下去。
那么这里给出这样一种情况:(2,1,3,4,5,6,7,8,9,10)第一次循环交换之后我们会发现,后面的数组已经有序,不再需要我们进行冒泡,后面的操作都是不必要的 这里我们只需要记录下最后一次进行交换的位置,那么下次遍历只要遍历到这个位置就可以结束。
代码进一步优化如下:
void BubbleSort(int *arr, int n)
{

                 int flag = n ;//第一次遍历终点为数组尾
                 while (flag > 0)
                {
                                 int k = flag;
                                flag = 0;
                                 for (int j = 1; j < k; ++j)
                                {
                                                 if (arr [j - 1] > arr[j])
                                                {
                                                                swap( arr[j - 1], arr [j]);
                                                                flag = j;//后面的已经排序好 记录下下一次排序的终点
                                                }
                                }
                }
}
虽然有了这么多优化,但是冒泡排序总体还是一种效率很低的排序,数据规模过大时不适合使用这种排序


堆排序:
堆的定义:
               1.堆是一颗完全二叉树
               2.父结点总是大于或等于(小于或等于)任何一个子节点
               3每个结点的左子树和右子树都是一个二叉堆
               当父结点总是大于或等于任何一个子节点值时为最大堆。反之则为最小堆
堆的结构示意图如下:
存储方式:
我们使用数组来存储一个堆,可以看出设父节点下标值为i 其左孩子下标值为2*i+1,右孩子为2*1+2;
代码实现如下:
void AdJust_down(int *arr, int parent, int size )
{
                 int child = 2 * parent +1;
                 while (child<size )
                {
                                 if (child+1<size &&arr[child+1]> arr[child])
                                {
                                                ++child;
                                }
                                 if (arr [parent]> arr[child])
                                                 break;
                                swap( arr[parent ], arr[child]);
                                 parent = child;
                                child = 2 * parent;
                }
}
void HeapSort(int *arr,int n)
{
                 for (int i = n/2-1; i >= 0; --i)
                {
                                AdJust_down( arr, i, n );
                }
                 for (int i = n - 1; i >= 1; --i)
                {
                                swap( arr[0], arr [i]);
                                AdJust_down( arr, 0, i);
                }
}
思路分析:
1.如果要对数字进行升序,我们首先首先将数组初始化为原始大堆
                 for (int i = n/2-1; i >= 0; --i)
                {
                                AdJust_down( arr, i, n );//从最后一个非叶子节点开始调整
                }
2.进行排序(以升序为例)
大堆的性质为:最大的数据一定在堆顶将堆顶和堆最后一个元素进行交换,则最大的数字此时在数字尾部,再将堆顶元素下调,且堆的大小减1,知道堆大小为1循环结束,排序完成。
代码如下:
                 for (int i = n - 1; i >= 1; --i)
                {
                                swap( arr[0], arr [i]);
                                AdJust_down( arr, 0, i);
                }

选择排序
选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。 选择排序是不稳定的排序方法
为了减少比较的次数,我们一次遍历同时选出最大值和最小值,代码实现如下:
void SelectSort(int *arr, int n)
{
                
                 int i = 0, j = n - 1;
                 int max = j;
                 int min = i;
                 int left = 0; int right = n - 1;
                 while (left<=right)
                {
                                min = left;
                                max = right; ///!!!!!!!!!!!重点
                                 for (i = left, j = right; i <= j; i++)
                                {
                                                 if (arr [min]>arr[i])
                                                                min = i;
                                                 if (arr [max] < arr[i]) ////{ 2, 9, 6, 1, 3, 4, 5, 7, 0 ,-8,1,-2}
                                                                max = i;
                                }
                                 if (left != min)
                                {
                                                swap( arr[left], arr [min]);
                                                 if (max == left)
                                                                max = min;
                                }
                                                
                                 if (right != max)
                                                swap( arr[right], arr [max]);
                                left++;
                                right--;
                }

}
这里我们必须注意到,以升序为例,如果一次遍历找到的最大值刚好在数组左边,此时肯定会先被移走,此时就最大值得下标就得更新为转移后的位置


快速排序:
该方法的基本思想是:
1.先从数列中取出一个数作为key。
2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
3.再对左右区间重复第二步,直到各区间只有一个数。
代码实现如下:
int PartSort(int *arr, int left, int right)
{
                 int key = arr [right]; //{ 10,2, 8, 9, 6, 1, 3, 4, 5, 7, 0 ,-1,-2,-100};
                 int begin = left ;
                 int end = right - 1;
                 while (begin<end)
                {
                                 while (begin<end&&arr [begin]<=key)
                                {
                                                begin++;
                                }
                                 while (begin<end&&arr [end]>=key)
                                {
                                                end--;
                                }
                                 if (begin < end)
                                                swap( arr[begin], arr [end]);
                }
                 if (arr [begin]>arr[right])
                {
                                swap( arr[begin], arr [right]);
                                 return begin;
                }
                 return right ;

}
void QuickSort(int *arr, int left,int right)
{              
                 if (left >= right)
                                 return;
                 int div = PartSort(arr , left, right);
                QuickSort( arr, div + 1, right );
                QuickSort( arr, left , div - 1);

}


以上是关于常见经典排序算法的主要内容,如果未能解决你的问题,请参考以下文章

经典算法复习快速排序的应用

常见14种经典排序算法(Java代码实现)

七种常见经典排序算法总结(C++实现)

七种常见经典排序算法总结(C++)

整理常见排序算法及其时间复杂度总结

十大经典排序算法