九大排序算法

来源:互联网 发布:淘宝转运网站 编辑:程序博客网 时间:2024/04/28 01:14

九大排序算法
- 冒泡排序
- 插入排序
- 选择排序
- 快速排序
- 归并排序
- 堆排序
- 计数排序
- 基数排序
- 桶排序

注:
1. 以下遍历中用到了swap()函数,进行两个数的交换
2. 其中的伪代码参考自《算法导论第三版》


冒泡排序

冒泡排序是一种简单的排序算法。它重复地“走访”要排序的数列,一次比较两个元素,如果他们的顺序错误就进行交换。

时间复杂度:O(n^2)
最优时间复杂度:O(n)
平均时间复杂度:O(n^2)
空间复杂度:O(n),
需要辅助空间:O(1)

实现思路

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

实现代码

template <typename T>void bubbleSort(T a[], int n)   // n为数组的大小{    for(int i = n; i >= 0; i--)    {        for(int j = 0; j < i; j++)        {            if(a[j] > a[j+1])                swap(a[j], a[j+1]);        }    }}

当然这种也可以

template <typename T>void bubbleSort(T a[], int n){    for(int i = 0; i < n; i++)        for(int j = 0; j < n - 1; j++)            if(a[j] > a[j+1])                swap(a[j], a[j+1]);}

插入排序

当我们对少量元素进行排序的时候,插入排序不失为一种有效而又方便的排序算法

插入排序的伪代码

INSERTSORT(A)for j = 2 to A.length    key = A[j];    i = j - 1;    while i > 0 and A[i] > key        A[i + 1] = A[i]        i = i -1    A[i + 1] = key

实现思路

  1. 从第一个元素开始,该元素可以认为已经被排序
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
  5. 将新元素插入到该位置后
  6. 重复步骤2~5

或者更通俗的讲,你可以将数组分类两部分,一部分为有序的即数组的第一个元素, 另一部分为除有序部分之外的元素。例如:a[0], a[1, n]。每次循环与有序的部分进行比较,插入有序的部分。

图解

插入排序

代码实现

template <typename T>void insertSort(T a[], int n){    for(int i = 1; i < n; i++)    {        T t = a[i];        int j;        for(j = i -1 ; j >= 0 && t < a[j]; j--) // 这里循环遍历有序数组,把t插入有序数组            a[j+1] = a[j];        a[j+1] = t;    }}

选择排序

选择排序是一种简单直观的排序算法。

实现思路

它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

图解

任然拿上面的数组进行演示,这里以每次查找最大值为例

选择排序

代码实现

template <typename T>int findindex(T a[], int n){    int maxindex = 0;    for(int i = 1; i < n; i++)    {        if(a[maxindex] < a[i])            maxindex = i;    }    return maxindex;}template <typename T>void selectSort(T a[], int n){    for(int i = n; i > 1; i--)    {        int maxindex = findindex(a, i);        swap(a[maxindex], a[i - 1]);    }}

当然如果你每次查找最小值的话,代码如下

template <typename T>void selectionSort(T a[], int n){    for(int i = 0; i < n - 1; i++)    {        int min = i;        for(int j = i + 1; j < n; j++)        {            if(a[j] < a[min])                min = j;        }        swap(a[i], a[min]);    }}

快速排序(快排)

快速排序通常是实际排序引用中最好的选择,因为它平均性能非常好

实现思路

  1. 将数组A[p…r]划分为两个子数组A[p…q-1]和A[q+1 … r],使得A[p…q-1]的每一个元素都小于A[q],而A[q+1 … r]的每一个元素都大于A[q]
  2. 通过递归调用快速排序,对子数组A[p…q-1]和A[q+1 … r]进行排序

伪代码

分为两段,一段递归调用,一段进行数组划分

QUICKSORT(A, p, r)    if p < r        q = PARTITION(A, p, r)        QUICKSORT(A, p, q-1)        QUICKSORT(A, q+1, r)
PARTITION(A, p, r)    x = A[r]    i = p - 1    for j = p to r - 1        if A[j] <= x            i = i + 1            exchange A[i] with A[j]    exchange A[i + 1] with A[r]    return i + 1

图解

快速排序

再分别对灰色和黑色数组进行快速排序,形成递归

代码实现

int partition(int a[], int p, int r){    int x = a[r];    int i = p - 1;    for(int j = p; j < r; j++)    {        if(a[j] <= x)        {            i += 1;            swap(a[i], a[j]);        }    }    swap(a[i+1], a[r]);    return i + 1;}void quickSort(int a[], int p, int r){    if(p < r)    {        int q = partition(a, p, r);     // 第一次找出图中黄颜色的下标        quickSort(a, p, q - 1);         // 递归灰色数组        quickSort(a, q + 1, r);         // 递归黑色数组    }}

归并排序

归并排序算法完全遵循分治模式。而分治模式的三个步骤分别为“分解”,“解决”,“合并”。其中归并算法的关键操作是“合并”。

实现思路

假设我们有一副牌分为A,B两幅,并且每一副牌都已经排序好
1. 我们逐个比较两幅牌,如果A的第一张牌小于B的第一张牌,则A的第一张牌入队
2. 继续比较,A的第二张牌和B的第一张牌,较小的一张牌入队,如此重复直到遍历完所有牌

图解

归并排序

然而我们大多数情况下, 一个数组分为两个小数组不可能两个数组都排序好的,那么我们就应该一步一步的往上推

思路和原来一样,只是现在的对象变为A(B),然后将A(B)再分为两副牌,对A(B)进行排序,一直往下递归,直至排序好。

归并排序2

伪代码

MERGE(A, p, q, r)    n1 = q - p + 1    n2 = r - 1    let L[1...n1 + 1] and R[1...n2 + 1] be new arrays    for i = 1 to n1        L[i] = A[p + i - 1]    for j = 1 to n2        R[j] = A[q + j]    L[n1 + 1] = -∞    R[n2 + 1] = -∞    for k = p to r        if L[i] <= R[j]            A[k] = L[i]            i = i + 1        else            A[k] = R[j]            j = j+1
MERGE-SORT(A, p, r)    if p < r        q = [(p + r) / 2]        MERGE-SORT(A, p, q)        MERGE-SORT(A, q + 1, r)        MERGE(A, p, q, r)

代码实现

void merge(int Array[], int p, int q, int r){    int n1 = q - p + 1;    int n2 = r - q;    int* lArray = new int[n1];    int* rArray = new int[n2];    for(int i = 0; i < n1; ++i) {        lArray[i] = Array[p + i];    }    for(int i = 0; i < n2; ++i) {        rArray[i] = Array[q + i + 1];    }    int i = 0;    int j = 0;    int k = p;    while(i < n1 && j < n2) {        if(lArray[i] <= rArray[j]) {            Array[k] = lArray[i];            i++;        }        else {            Array[k] = rArray[j];            j++;        }        k++;    }    while(i < n1) {        Array[k++] = lArray[i++];    }    while(j < n2) {        Array[k++] = rArray[j++];    }}void mergeSort(int Array[], int p, int r){    if(p < r) {        int q = p + (r - p) / 2;        mergeSort(Array, p, q);        mergeSort(Array, q + 1, r);        merge(Array, p, q, r);    }}

堆排序

要想理解堆排序首先需要知道什么是堆,这里我们利用二叉(最大)堆进行排序。二叉堆可以近似看成一个完全二叉树。其中又分为最大堆和最小堆。在最大堆中,最大堆性质是指除了根以外的所有节点i都要满足: A[parent(i)] >= A[i]。也就是说堆中的最大元素存放在根节点中。而最小堆真好相反。

实现思路

  1. 首先根据推导我们很容易知道它的父节点,左孩子和右孩子下标的计算方式.用于维护最大堆
// 父节点int parent(int i){    return i / 2;}// 左子树int left(int i){    return 2 * i;}// 右子树int right(int i){    return 2 * i + 1;}
  1. 建立一个最大二叉堆,并且在建立最大堆的同时要维护最大堆的性质
  2. 由于最大堆的性质,他的堆中最大元素肯定处于顶层根节点中,因此每次只需要把最大元素取出来,在对剩下的元素重新维护,形成新的最大堆,再接着取最顶层的最大值,依次类推,直到最后一个
实现代码
#include <iostream>#include <climits>using namespace std;int parent(int i){    return i / 2;}int left(int i){    return 2*i;}int right(int i){    return 2*i + 1;}/* 维护堆 */void Max_HeapIfy(int a[], int i, int heap_size){    int l = left(i);    int r = right(i);    int largest;    if(l <= heap_size && a[l] > a[i])        largest = l;    else        largest = i;    if(r <= heap_size && a[r] > a[largest])        largest = r;    if(largest != i)    {        swap(a[i], a[largest]);        Max_HeapIfy(a, largest, heap_size);    }}/* 建立最大堆 */void Build_Max_Heap(int a[], int heap_size){    for(int i = heap_size / 2; i > 0; i--)        Max_HeapIfy(a, i, heap_size);}/* 堆排序 */void HeapSort(int a[], int heap_size){    Build_Max_Heap(a, heap_size);    for(int i = heap_size; i >= 0; i--)    {        swap(a[0], a[i]);        heap_size -= 1;        Max_HeapIfy(a, 0, heap_size);    }}int main(){    int a[11] = {INT_MIN , 4, 5, 3, 3, 1, 19, 10, 14, 8, 7};    HeapSort(a, 10);    for(int i = 0; i < 10; i++)         // for(int i = 0; i < 11; i++) 如果改成这样子会怎么样?        cout << a[i] << " ";    cout << endl;    return 0;}

计数排序

在前面介绍的6中排序都有一个共同的特点:在排序的最终结果中,各元素的次序依赖于它们之间的比较。所以我们又把这类排序叫做比较排序。而计数排序和基数排序,以及接下来的桶排序则不是运用比较来确定排序顺序的。

实现思路

计数排序假设n个输入元素中的每一个都是在0到k区间的一个整数,其中k为某个整数。

对每一个输入元素x,确定小于x的元素个数。利用这一信息,就可以直接把x放到它在输出数组的位置上了。例如,如果有17个元素小于x,则x就应该放在第18个输出位置上。

伪代码

COUNT-SORT(A, B, k)let C[0...k] be a new arrayfor i = 0 to k    C[i] = 0for j = 1 to A.length    C[A[j]] = C[A[j]] + 1for i = 1 to k    C[i] = C[i] + C[i - 1]for j = A.length downto 1    B[C[A[j]]] = A[j]    C[A[j]] = C[A[j]] - 1

由于代码中存在三个for循环,我个人喜欢叫它为三for排序

代码实现

void CountingSort(int a[], int b[], int k, int arr_size){    int *c  = new int[k + 1]{0};    for(int j = 0; j < arr_size; j++)        c[a[j]]  = c[a[j]] + 1;    for(int i = 1; i <= k; i++)        c[i] = c[i] + c[i-1];    for(int j = arr_size - 1; j >= 0; j--)    {        b[c[a[j]]] = a[j];        c[a[j]] = c[a[j]]-1;    }    delete[] c;}

基数排序

基数排序是先按最低有效位进行排序的排序算法

实现思路

  1. 从最低有效位进行排序,如下图的个位开始排序
  2. 个位排序完排十位,接下来是百位

而每一个位的排序则用到了计数排序的思路

图解

基数排序

代码实现

// 假设n个d位的元素存放在数组A中,其中第一位是最低位,第d位是最高位void radixSort(int a[], int b[], int len, int d)    {    int *arrC = new int[10]; //most 10 numbers;    int radix = 1;     for (int k = 1; k <= d; k ++) {        //using CountingSort for every bit;        for (int i = 0; i < 10; i ++)            arrC[i] = 0;        for (int i = 0; i < len; i ++) {            int temp = (a[i]/radix) % 10; //find every bit of a number            arrC[temp] = arrC[temp] + 1;        }        for (int i = 1; i < 10; i ++)            arrC[i] = arrC[i] + arrC[i-1];        for (int j = len-1; j >= 0; j --) {            int temp = (a[j]/radix) % 10;            b[arrC[temp] - 1] = a[j];            arrC[temp] = arrC[temp] - 1;        }        radix *= 10; //another bit;        memcpy(a, b, len * sizeof(int)); //note this line;    }}

桶排序

这里利用链表存储数组元素

实现思路

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

代码实现

typedef struct BucketNode{    double        nValue;    BucketNode    *pNext;  }Node;void BucketSort_Link(double arrA[], int nLen){    Node *pBucket = new Node[10];    //initial the bucket    for (int i = 0; i < 10; i ++) {        pBucket[i].nValue = 0.0;        pBucket[i].pNext = NULL;    }    for (int i = 0; i < nLen; i ++) {        double nTemp = arrA[i];        Node *pNode = new Node();        pNode->nValue = nTemp;        pNode->pNext = NULL;        int nKey = int(arrA[i]*10);        if (pBucket[nKey].pNext == NULL) {            pBucket[nKey].pNext = pNode; //每个桶的第一个位置不存数据        }        else {            Node *p = &pBucket[nKey]; //p-->q            Node *q = pBucket[nKey].pNext;            while (q && q->nValue <= nTemp) {                p = q;                q = q->pNext;            }            pNode->pNext = q;            p->pNext = pNode;        }    }    int k = 0;    for (int i = 0; i < 10; i ++) {        Node *pTemp = pBucket[i].pNext;        while (pTemp) {            arrA[k ++] = pTemp->nValue;            pTemp = pTemp->pNext;        }    }}
0 0