八大排序算法

Posted csdn_lisword

tags:

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

八大排序算法


1. 插入排序—直接插入排序 ( 稳定的 O(nlogn) )

基本思想:

将一个记录插入到已排序好的有序表中,从而得到一个新,记录数增1的有序表。即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。

要点:设立哨兵,作为临时存储和判断数组边界之用。

如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。

算法实现:

// 1. 直接插入排序
void InsertSort(vector<int> &num) {
    int low = 0, high = num.size()-1;
    for (int i = low + 1; i <= high; ++i)
    {
        if (num[i] < num[i - 1]) {
            int j = i;
            while (j > low && num[j] < num[j - 1]) {
                swap(num[j], num[j - 1]);
                j--;
            }
        }
    }
}

2. 插入排序—希尔排序(Shell`s Sort) ( 不稳定 O(n1.3) )

基本思想:

先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。

操作方法:

  1. 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
  2. 按增量序列个数k,对序列进行k 趟排序;
  3. 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

算法实现:

void ShellInsertSort(vector<int> &num, int n, int dk) {
    for (int i = dk; i < n; ++i) {
        int j = i;
        while (j - dk >= 0 && num[j] < num[j - dk]) {
            swap(num[j], num[j - dk]);
            j -= dk;
        }
    }
}
void ShellSort(vector<int> &num) {
    int dk = num.size() / 2;
    while (dk >= 1) {
        ShellInsertSort(num, num.size(), dk);
        dk /= 2;
    }
}

3. 选择排序—简单选择排序(Simple Selection Sort) (不稳定 O(n2) )

基本思想:

在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。

操作方法:

第一趟,从n 个记录中找出关键码最小的记录与第一个记录交换;
第二趟,从第二个记录开始的n-1 个记录中再选出关键码最小的记录与第二个记录交换;
以此类推…..
第i 趟,则从第i 个记录开始的n-i+1 个记录中选出关键码最小的记录与第i 个记录交换,
直到整个序列按关键码有序。

算法实现:

int SelectMinKey(vector<int> &num, int i) {
    int minKey = i;
    for (int j = i + 1; j < num.size(); ++j) {
        minKey = num[j] < num[minKey] ? j : minKey;
    }
    return minKey;
}
void SimpleSelectSort(vector<int> &num) {
    for (int i = 0; i < num.size(); ++i) {
        int minKey = SelectMinKey(num, i);
        if (minKey != i)
            swap(num[minKey], num[i]);
    }
}



4. 选择排序—堆排序(Heap Sort) ( 不稳定的 O(nlogn) )

基本思想:

堆的定义如下:具有n个元素的序列(k1,k2,...,kn),当且仅当满

{kik2i+1kik2i+2
或者,
{kik2i+1kik2i+2

时称之为堆(前者为小顶堆,后者为大顶堆)。

堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。

堆的存储

一般都用数组来表示堆,i结点的父结点下标就为(i1)/2。它的左右子结点下标分别为2i+12i+2。如第0个结点左右子结点下标分别为1和2。

小顶堆的插入

// 新加入i结点  其父结点为(i - 1) / 2  
void MinHeapFixup(vector<int> &a, int i)  
{  
    int j = (i - 1) / 2, temp = a[i];  
    while (j >= 0 && i != 0)  
    {   //把较大的子结点往下移动,替换它的子结点
        if (a[j] <= temp)  break; 
        a[i] = a[j];       
        i = j;  
        j = (i - 1) / 2;  
    }  
    a[i] = temp;  
}  
// 更简短的表达:
void MinHeapFixup2(vector<int> &a, int i)  
{  
    for (int j = (i - 1) / 2; (j >= 0 && i != 0)&& a[i] > a[j]; i = j, j = (i - 1) / 2)  
        Swap(a[i], a[j]);  
}  
// 在最小堆中加入新的数据nNum  
void MinHeapAddNumber(vector<int>, int nNum)  
{  
    a.push_back(nNum);  
    MinHeapFixup(a, a.size()-1);  
}  

堆的删除

堆中每次都只能删除第0个数据。为了便于重建堆,实际的操作是将最后一个数据的值赋给根结点,然后再从根结点开始进行一次从上向下的调整。调整时先在左右儿子结点中找最小的,如果父结点比这个最小的子结点还小说明不需要调整了,反之将父结点和它交换后再考虑后面的结点。

//  从i节点开始调整,n为节点总数 从0开始计算 i节点的子节点为 2*i+1, 2*i+2  
void MinHeapFixdown(vector<int> &a, int i, int n)  
{  
    int j, temp;  
    temp = a[i];  
    j = 2 * i + 1;  
    while (j < n)  
    {  
        if (j + 1 < n && a[j + 1] < a[j]) //在左右孩子中找最小的  
            j++;  

        if (a[j] >= temp)  
            break;  

        a[i] = a[j];     //把较小的子结点往上移动,替换它的父结点  
        i = j;  
        j = 2 * i + 1;  
    }  
    a[i] = temp;  
}  
//在最小堆中删除数  
void MinHeapDeleteNumber(vector<int> &a,int n)  
{  
    Swap(a[0], a[n - 1]);  
    MinHeapFixdown(a, 0, n - 1);  
}  

建立小顶堆

void MakeMinHeap(vector<int> &a, int n)  
{  
    for (int i = n / 2 - 1; i >= 0; i--)  
        MinHeapFixdown(a, i, n);  
} 

堆排序

// 小顶堆排序后的数组是递减的,若要递增,使用大顶堆
void Minheapsort(vector<int> &a, int n)  
{   
    MakeMinHeap(a, n); 
    for (int i = n - 1; i >= 1; i--)  
    {  
        Swap(a[i], a[0]);  
        MinHeapFixdown(a, 0, i);  
    }  
}  

完整的堆排序

// 4. 堆排序
void MaxHeapFixdown(vector<int> &a, int i, int n)  
{  
    int j = 2 * i + 1, temp = a[i];  
    while (j < n)  
    {  
        if (j + 1 < n && a[j + 1] > a[j]) //在左右孩子中找最大的  
            j++;  

        if (a[j] <= temp)  
            break;  

        a[i] = a[j];     //把较大的子结点往上移动,替换它的父结点  
        i = j;  
        j = 2 * i + 1;  
    }  
    a[i] = temp;  
}  
void MakeMaxHeap(vector<int> &a, int n)  
{  
    for (int i = (n-1)/2; i >= 0; i--)  
        MaxHeapFixdown(a, i, n);  
}  
void Maxheapsort(vector<int> &a, int n)  
{  
    MakeMaxHeap(a, n);  
    for (int i = n - 1; i > 0; i--)  
    {  
        swap(a[i], a[0]);  
        MaxHeapFixdown(a, 0, i);  
    }  
} 

5. 交换排序—冒泡排序(Bubble Sort) ( 稳定的 O(n2) )

基本思想:

在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。

算法实现:

void BubbleSort(vector<int> &a) {
    int n = a.size();
    for(int i = 0;i<n;++i)
    for(int j=0;j<n-i-1;++j){
        if(a[j] > a[j+1])
            swap(a[j],a[j+1]);
    }
}  

6. 交换排序—快速排序(Quick Sort) ( 不稳定的 O(nlogn) )

基本思想:

1)选择一个基准元素,通常选择第一个元素或者最后一个元素,
2)通过一趟排序讲待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准元素值小。另一部分记录的 元素值比基准值大。
3)此时基准元素在其排好序后的正确位置
4)然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序。

快速排序的示例:

(a)一趟排序的过程:
初 始 关 键 字 : (49) 38 65 97 76 13 27 49
进 行 1 次 交 換: 27 38 65 97 76 13 (49) 49
进 行 2 次 交 换: 27 38 (49) 97 76 13 65 49
迸 行 3 次 交 換: 27 38 13 97 76 (49) 65 49
进 行 4 次 交 换: 27 38 13 (49) 76 97 65 49

算法实现:

int Partition(vector<int> &a, int low, int high) {
    int pivotKey = a[low]; // 这里pivotKey记录的一定是值,不能是下标,因为下标的值可能会换
    while (low < high) {
        while (low < high && a[high] >= pivotKey) high--;
        swap(a[high], a[low]);
        while (low < high && a[low] <= pivotKey) low++;
        swap(a[high], a[low]);
    }
    return low;
}
void QuickSort(vector<int> &a, int low, int high) {
    if (low < high) {
        int pivotLoc = Partition(a, low, high);
        QuickSort(a, low, pivotLoc - 1);
        QuickSort(a, pivotLoc + 1, high);
    }
}

7. 归并排序(Merge Sort) ( 稳定的 O(nlogn) )

基本思想:

归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

算法实现:

// 7. 归并排序
//将二个有序数列a[first...mid]和a[mid...last]合并。
void MergeArray(vector<int> &array, int low, int mid, int high)
{
    int i = low; // i是第一段序列的下标
    int j = mid + 1; // j是第二段序列的下标
    int k = 0; // k是临时存放合并序列的下标
    vector<int> tmp(high - low + 1); // array2是临时合并序列
    // 扫描第一段和第二段序列,直到有一个扫描结束
    while (i <= mid && j <= high) {
        // 判断第一段和第二段取出的数哪个更小,将其存入合并序列,并继续向下扫描
        if (array[i] <= array[j]) {
            tmp[k++] = array[i++];
        } else {
            tmp[k++] = array[j++];
        }
    }
    // 若第一段序列还没扫描完,将其全部复制到合并序列
    while (i <= mid) {
        tmp[k++] = array[i++];
    }
    // 若第二段序列还没扫描完,将其全部复制到合并序列
    while (j <= high) {
        tmp[k++] = array[j++];
    }
    // 将合并序列复制到原始序列中
    for (i = 0; i < k; i++)
        array[low + i] = tmp[i];
}
void MergeSort(vector<int> &a, int low, int high)
{
    if (low < high)
    {
        int mid = (low + high) / 2;
        MergeSort(a, low, mid);    //左边有序
        MergeSort(a, mid + 1, high); //右边有序
        MergeArray(a, low, mid, high); //再将二个有序数列合并
    }
}

8. 桶排序/基数排序(Radix Sort) ( 稳定的 O(n) )

基本思想:

是将阵列分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递回方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的阵列内的数值是均匀分配的时候,桶排序使用线性时间(O(n))。但桶排序并不是 比较排序,他不受到 O(n log n) 下限的影响。

算法实现:

// 8. 桶排序
//找到num的从低到高的第pos位的数据
int GetNumInPos(int num, int pos)
{
    int temp = 1;
    for (int i = 0; i < pos - 1; i++)
        temp *= 10;

    return (num / temp) % 10;
}
#define RADIXNUM 10   // 基数(桶的个数)
#define KEYNUM 10     // 关键字个数,这里为最大整数的位数
void RadixSort(vector<int> &a)
{
    int n = a.size();
    vector<vector<int> > bucket(RADIXNUM, vector<int>(n)); // 分配桶空间
    for (int pos = 1; pos <= KEYNUM; pos++)    //从个位到最高位
    {
        for (int i = 0; i < n; i++)    //分配过程
        {
            int num = GetNumInPos(a[i], pos);
            int index = ++bucket[num][0];
            bucket[num][index] = a[i];
        }

        for (int i = 0, j = 0; i < RADIXNUM; i++)   //收集
        {
            for (int k = 1; k <= bucket[i][0]; k++)
                a[j++] = bucket[i][k];
            bucket[i][0] = 0;    //复位
        }
    }
}

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

C语言实现八大排序算法

数据结构初阶第九篇——八大经典排序算法总结(图解+动图演示+代码实现+八大排序比较)

数据结构初阶第九篇——八大经典排序算法总结(图解+动图演示+代码实现+八大排序比较)

八大排序算法C语言过程图解+代码实现(插入,希尔,选择,堆排,冒泡,快排,归并,计数)

八大经典排序算法的代码实现

《糊涂算法》之八大排序——希尔排序