浅谈经典排序算法系列—各种排序算法总结

排序算法的稳定性定义
假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,ri=rj,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的。

稳定性判定方法:

对于不稳定的排序算法,只要举出一个实例,即可说明它的不稳定性;而对于稳定的排序算法,必须对算法进行分析从而得到稳定的特性。需要注意的是,排序算法是否为稳定的是由具体算法决定的,不稳定的算法在某种条件下可以变为稳定的算法,而 稳定的算法在某种条件下也可以变为不稳定的算法
例如, 对于如下起泡排序算法,原本是稳定的排序算法,如果将记录交换的条件改成r[j]>=r[j+1],则两个相等的记录就会交换位置,从而变成不稳定的算法

排序算法——冒泡法排序

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

最坏时间复杂度 O(n^2)

冒泡排序是稳定的排序算法。

#include <stdio.h>   
#define bool int   
#define false 0   
#define true 1   
  
void bubble_sort(int a[], int num)   
{   
    int i, j, k;   
    for (i = 0; i < num; i++) {   
        bool flag = false;      /* mark for need bubble sort*/  
        for (j = 0; j < num - i - 1; j++) {   
            if (a[j] > a[j+1]) {   
                flag = true;   
                int tmp = a[j];   
                a[j] = a[j+1];   
                a[j+1] = tmp;   
            }   
        }   
        if (!flag)    /* if this turn have no exchange.end sort*/  
            break;   
        print(a, num);   
    }   
                   
}   
void print(int a[], int num)   
{   
    for (k = 0; k < num; k++)   
        printf("%d ", a[k]);   
    printf("/n");   
}   
int main()   
{   
    int i, num;   
    int a[] = {4,7,3,8,2,1,9,6,5,10};   
    num = sizeof(a) / sizeof(int);   
    bubble_sort(a, num);   
    print(a, num);   
    return 0   
}   
第10行设置了flag,一旦发现不用再交换元素则终止循环,提高了效率。

排序算法——选择排序

选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到排序序列起始位置。以此类推,直到所有元素均排序完毕。

最坏时间复杂度 O(n^2)

选择排序是不稳定的排序算法。例如5 8 5 2 9。第一次选择5 与 2交换 ,之后两个5的相对位置就改变了。 

#include <stdio.h>   
    
void select_sort(int a[], int num)   
{   
  
    int i, j, k, min, temp;   
  
    for (i = 0; i < num - 1; i++) {   
        min = i;   
        for (j = i + 1; j < num; j++) {   
            if (a[min] > a[j]) {   
                min = j;   
            }   
        }   
        temp = a[i];   
        a[i] = a[min];   
        a[min] = temp;   
    }   
}   
void print(int a[], int num)   
{   
    int k;   
    for (k = 0; k < num; k++)   
        printf("%d ", a[k]);   
    printf("/n");   
}   
int main()   
{   
    int i, num;   
    int a[10] = {1,7,5,8,4,2,6,9,3,10};   
    num = sizeof(a) / sizeof(int);   
    select_sort(a, num);   
    print(a, num);   
    return 0;   
}  
 8-18行代码,每次遍历记录最小值的下标,最终选取的是当前剩余序列的最小值。

排序算法——插入排序

插入排序(Insertion Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。(插入排序的前提是逐步构造有序表

最坏时间复杂度 O(n^2)

插入排序是稳定的排序算法。

#include <stdio.h>

void insert_sort(int a[], int num)
{
    int i, j, temp;
    for (i = 1; i < num; i++)    //共需进行num-1次插入排序操作
	{
        temp = a[i];             //记录插入新元素的值temp

        for (j = i - 1; j >= 0 && a[j] > temp; j--) //由后往前从已有序的序列中寻找temp放置的位置
		{
            a[j+1] = a[j];
        }
        a[j+1] = temp;  //将新插入元素a[i]放到找到的位置
    }
}
void print(int a[], int num)
{
    int i;
    for (i = 0; i < num; i++) {
        printf("%d ", a[i]);
    }
    printf("\n");
}
int main()
{
    int i, num;
    int a[10] = {1,7,5,8,4,2,6,9,3,10};
    num = sizeof(a) / sizeof(int);
    insert_sort(a, num);
    print(a, num);
    return 0;
}

排序算法——二分插入排序

二分插入排序用于在插入排序中减少比较操作的位置,是插入排序的一个变种。

最坏时间复杂度 O(n^2)

#include <stdio.h>

void insert_sort(int a[], int num)
{
    int i, j, k, temp, left, right, middle;
    for (i = 1; i < num; i++) {
        temp = a[i];

        left = 0;   /*search for insert pos*/
        right = i - 1;
        while (left <= right)   //二分法从,查找新插入的元素待放置位置区间[left,right]
		{
            middle = (left + right) / 2;
            if (temp > a[middle])
                left = middle + 1;
            else
                right = middle -1;
        }
		//最终while循环中left会出现在right的右边,因此temp>=a[right],且temp<=a[left]
		printf("left=%d right=%d\n",left , right);
        for (j = i - 1; j > right; j--)
            a[j+1] = a[j];

        a[left] = temp;
    }
}
main()
{
    int i, num;
    int a[] = {8,2,9,5,6,7,5};
    num = sizeof(a) / sizeof(int);
    insert_sort(a, num);

    for (i = 0; i < num; i++)
        printf("%d ", a[i]);
    printf("\n");
}

排序算法——合并排序

合并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
最坏时间复杂度 O(nlogn), 合并排序是稳定的排序算法

#include <stdio.h>
#include <stdlib.h>

void merge(int a[], int p, int q, int r)
{
    int i, j, k;
    int *temp = (int *)malloc((r - p + 1) * sizeof(int));

    i = p;
    j = q + 1;
    k = 0;
    while ((i <= q) && (j <= r))
	{
        if (a[i] < a[j])
            temp[k] = a[i++];
        else
            temp[k] = a[j++];
        k++;
    }
    while (i <= q)
        temp[k++] = a[i++];
    while (j <= r)
        temp[k++] = a[j++];

    for (i = 0; i < r - p + 1; i++)
	{
        a[p+i] = temp[i];
    }
    free(temp);
}
void merge_sort(int a[], int p, int r)
{
    int q;
    if (p < r) {
        q = (p + r) / 2;
        merge_sort(a, p, q);
        merge_sort(a, q + 1, r);
        merge(a, p, q, r);
    }
}

int main()
{
    int a[] = {5,2,4,7,1,3,2,6}, i;
    int p = 0, r = sizeof(a) / sizeof(int);
    
    merge_sort(a, p, r);

    for (i = 0; i < r; i++)
        printf("%d ", a[i]);
        
    return 0;
}

排序算法——堆排序(递归版本)

堆积排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法。堆积树是一个近似完整二叉树的结构,并同时满足堆积属性:即子结点的键值或索引总是小于(或者大于)它的父结点
在堆积树的数据结构中,堆积树中的最大值总是位于根节点。堆积树中定义以下几种操作:


1.最大堆积调整(max_heapify):将堆积树的末端子结点作调整,使得子结点永远小于父结点 
2.建立最大堆积(build_max_heap):将堆积树所有数据重新排序 
3.堆积排序(heap_sort):移除位在第一个数据的根结点,并做最大堆积调整的递归运算


最坏时间复杂度 O(nlogn),
堆排序是不稳定的排序算法

#include <stdio.h>

void max_heapify(int a[], int parent, int heap_size)
{
    int l, r, largest, temp;

    l = (parent << 1) + 1;
    r = l + 1;
    largest = parent;

    if (l <= heap_size && a[l] > a[largest])
	{
        largest = l;
    }
    if (r <= heap_size && a[r] > a[largest])
	{
        largest = r;
    }
    if (largest != parent)
	{
        temp = a[parent];
        a[parent] = a[largest];
        a[largest] = temp;
        max_heapify(a, largest, heap_size);
    }
}

void build_max_heap(int a[], int heap_size)
{
    int i;
    for (i = heap_size / 2; i >= 0; i--)
	{
        max_heapify(a, i, heap_size);
    }
}

void heap_sort(int a[], int heap_size)
{
    int i, temp;
    build_max_heap(a, heap_size);
    for (i = heap_size; i >= 0; i--)
    {
        temp = a[i];
        a[i] = a[0];
        a[0] = temp;
        heap_size--;
        max_heapify(a, 0, heap_size);
    }
}

int main()
{
    int a[] = {46,79,56,38,40,84}, i;
    int size = sizeof(a) / sizeof(int);
    heap_sort(a, size - 1);

    for (i = 0; i < size; i++) {
        printf("%d ", a[i]);
    }
    return 0;
}

排序算法——快速排序

快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。
步骤为:

1.从数列中挑出一个元素,称为 "基准"(pivot), 
2.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分割之后,该基准是它的最后位置。这个称为分割(partition)操作。 
3.递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。


最坏时间复杂度: O(n^2),平均时间复杂度:O(nlogn),快速排序是不稳定的排序算法。
#include <stdio.h>

void swap(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

int partition(int a[], int p, int q)
{
    int x, i, j;

    x = a[p];   /* x as a pivot */
    i = p;
    for (j = p + 1; j < q; j++)
	{
        if (a[j] <= x)
		{
            i++;
            swap(&a[i], &a[j]);
        }
    }
    swap(&a[p], &a[i]);
    return i;
}

void quick_sort(int a[], int p, int q)
{
    if (p < q)
	{
        int r = partition(a, p, q);
        quick_sort(a, p, r);  /* sort sub list a[p, r)  */
        quick_sort(a, r+1, q);/* sort sub list a[r+1,q) */
    }

}

int main()
{
    int a[] = {6, 10, 13, 5, 8, 3, 2, 11}, i;
    int size = sizeof(a) / sizeof(int);
    quick_sort(a, 0, size);

    for (i = 0; i < size; i++)
        printf("%d ", a[i]);
    return 0;
}
注意:
如果我们调用的是quick_sort(a, 0, size - 1),那么要做以下修改
code 16:j <= q
code 30:quick_sort(a, p, r-1)
code 31:quick_sort(a, r+1, q)

排序算法——计数排序

计数排序是一个非基于比较的线性时间排序算法。它对输入的数据有附加的限制条件:
1、输入的线性表的元素属于有限偏序集S; 
2、设输入的线性表的长度为n,|S|=k(表示集合S中元素的总数目为k),则k=O(n)。 

在这两个条件下,计数排序的复杂性为O(n+k)。

计数排序算法的基本思想是对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数。一旦有了这个信息,就可以将x直接存放到最终的输出序列的正确位置上。当然,如果有多个元素具有相同的值时,我们不能将这些元素放在输出序列的同一个位置上,因此,上述方案还要作适当的修改。
计数排序是稳定的排序算法。
#include <stdio.h>
#include <stdlib.h>

void counting_sort(int a[], int size)
{
    int i, j, min, max, range;
    int *c, *b;

    min = max = a[0];
    for (i = 1; i < size; i++)
	{/* find the range of input list */
        if (a[i] < min)
		{
            min = a[i];
        }
		else if (a[i] > max)
		{
            max = a[i];
        }
    }
    range = max - min + 1;
    c = (int *)malloc(range * sizeof(int));/* for temporary storage */
    b = (int *)malloc(size * sizeof(int)); /* for storage sorted list */

    for (i = 0; i < range; i++)
	{
        c[i] = 0;
    }

    /* c[i] include the num of elements which equal to i */
    for (j = 0; j < size; j++)
	{
        c[a[j] - min] += 1;
    }

    /* c[i] include the num of elements which less or equal than i */
    for (i = 1; i < range; i++)
	{
        c[i] += c[i-1];
    }

    for (j = size - 1; j >= 0; j--)
	{
        b[c[a[j] - min] - 1] = a[j];/* for c style */
        c[a[j] - min] -= 1;
    }

    /* input sorted list to array a. not necessary, we can return the address of array b */
    for (i = 0; i < size; i++)
        a[i] = b[i];
    free(b);
    free(c);
}

main()
{
    int a[] = {4, 1, 3, 4, 3}, i;
    int size = sizeof(a) / sizeof(int);
    counting_sort(a, size);

    for (i = 0; i < size; i++)
        printf("%d ", a[i]);
}
由于C数组的下标由0开始,这里用到下标转换有点麻烦
a:index  0 1 2 3 4
   value  4 1 3 4 3 
c:index  0 1 2 3
   value  1 0 2 2 (code 26-28)
   value  1 1 3 5 (code 31-33)
b:index  0 1 2 3 4
   value  1 3 3 4 4

排序算法——基数排序

基数排序(Radix sort)是一种排序算法,它是这样实现的:
所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零. 然后, 从最低位开始, 依次进行一次排序.这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列.
基数排序时间复杂度: O(d(n+k))  n个d位数,每个数位有k种取值
#include <stdio.h>
#include <stdlib.h>

int max_bit(int a[], int size) /* get the max bit of number */
{
    int i, max, b;

    for (i = 1, max = a[0]; i < size; i++)
	{
        if (a[i] > max)
		{
            max = a[i];
        }
    }
    b = 1;
    while (max / 10)
	{
        b++;
        max /= 10;
    }
    return b;
}

void radix_sort(int a[], int size)
{
    int d = max_bit(a, size);
    int i, j, k, range, radix;
    int *c, *b;

    range = 10; /* for counting sort, the range of every bit is 0 - 9 */
    radix = 1;
    c = (int *)malloc(sizeof(int) * range);
    b = (int *)malloc(sizeof(int) * size);
    for (i = 0; i < d; i++, radix *= 10)
	{

        /* use counting sort */
        /* clear count before every sort */
        for (j = 0; j < range; j++)
		{
            c[j] = 0;
        }

        /* c[k] content the num of elements which equal to k */
        for (j = 0; j < size; j++)
		{
            k = (a[j] / radix) % 10;
            c[k]++;
        }

        /* c[j] content the num of elements which equal or less than j */
        for (j = 1; j < range; j++)
            c[j] += c[j-1];

        /* put a[j] into the space of b[c[k] - 1] */
        for (j = size - 1; j >= 0; j--)
		{
            k = (a[j] / radix) % 10;
            b[c[k] - 1] = a[j];
            c[k]--;
        }

        /* copy the 'sorted' list to a[j] */
        for (j = 0; j < size; j++)
            a[j] = b[j];
    }
    free(c);
    free(b);
}

main()
{
    int a[] = {329, 457, 657, 839, 436, 720, 355}, i;
    int size = sizeof(a) / sizeof(int);
    radix_sort(a, size);

    for (i = 0; i < size; i++)
        printf("%d ", a[i]);
}

排序算法——桶排序


 桶排序 (Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将阵列分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递回方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的阵列内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是 比较排序,他不受到 O(n log n) 下限的影响。
      例如要对大小为[1..1000]范围内的n个整数A[1..n]排序,可以把桶设为大小为10的范围,具体而言,设集合B[1]存储[1..10]的整数,集合B[2]存储(10..20]的整数,……集合B[i]存储((i-1)*10, i*10]的整数,i = 1,2,..100。总共有100个桶。然后对A[1..n]从头到尾扫描一遍,把每个A[i]放入对应的桶B[j]中。 然后再对这100个桶中每个桶里的数字排序,这时可用冒泡,选择,乃至快排,一般来说任何排序法都可以。最后依次输出每个桶里面的数字,且每个桶中的数字从小到大输出,这样就得到所有数字排好序的一个序列了。   
      假设有n个数字,有m个桶,如果数字是平均分布的,则每个桶里面平均有n/m个数字。如果对每个桶中的数字采用快速排序,那么整个算法的复杂度是O(n+m*n/m*log(n/m))=O(n+nlogn-nlogm)   
      从上式看出,当m接近n的时候,桶排序复杂度接近O(n)   
      当然,以上复杂度的计算是基于输入的n个数字是平均分布这个假设的。这个假设是很强的,实际应用中效果并没有这么好。如果所有的数字都落在同一个桶中,那就退化成一般的排序了。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

extern void quick_sort(int a[], int p, int q);/* not necessary */

struct barrel
{
    int node[10];
    int count;/* the num of node */
};

void bucket_sort(int data[], int size)
{
    int max, min, num, pos;
    int i, j, k;
    struct barrel *pBarrel;

    max = min = data[0];
    for (i = 1; i < size; i++)
	{
        if (data[i] > max)
		{
            max = data[i];
        }
		else if (data[i] < min)
		{
            min = data[i];
        }
    }
    num = (max - min + 1) / 10 + 1;
    pBarrel = (struct barrel*)malloc(sizeof(struct barrel) * num);
    memset(pBarrel, 0, sizeof(struct barrel) * num);

    /* put data[i] into barrel which it belong to */
    for (i = 0; i < size; i++)
	{
        k = (data[i] - min + 1) / 10;/* calculate the index of data[i] in barrel */
        (pBarrel + k)->node[(pBarrel + k)->count] = data[i];
        (pBarrel + k)->count++;
    }

    pos = 0;
    for (i = 0; i < num; i++)
	{
        quick_sort((pBarrel+i)->node, 0, (pBarrel+i)->count);/* sort node in every barrel */

        for (j = 0; j < (pBarrel+i)->count; j++)
		{
            data[pos++] = (pBarrel+i)->node[j];
        }
    }
    free(pBarrel);
}

main()
{
    int data[] = {78, 17, 39, 26, 72, 94, 21, 12, 23, 91}, i;
    int size = sizeof(data) / sizeof(int);
    bucket_sort(data, size);

    for (i = 0; i < size; i++)
        printf("%d ", data[i]);
}
注意:注意这里的快速排序用上面的版本。

排序算法——希尔排序

        希尔排序(Shell Sort)也称为递减增量排序算法,是插入排序的一种高速而安定的改良版。因希尔(Donald L. Shell)于1959年提出而得名。各种实现在如何进行递减上有所不同。
        希尔排序是基于插入排序的以下两点性质而提出改进方法的
    插入排序在对几乎已经排好序的数据操作时, 效率高, 即可以达到线性排序的效率
    但插入排序一般来说是低效的, 因为插入排序每次只能将数据移动一位

        对有n个元素的可比较资料,先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-1<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。
该方法实质上是一种分组插入方法。
        在具体实现上,希尔排序的性能与所选取的分组长度序列有很大关系
#include <stdio.h>

void shell_sort(int a[], int size)
{
    int gap, temp;
    int i, j;

    for (gap = size / 2;gap > 0; gap /= 2)
	{
        for (i = gap; i < size; i++)
		{
            for (j = i - gap; j >= 0 && a[j] > a[j+gap]; j-=gap)
			{
                temp = a[j];
                a[j] = a[j+gap];
                a[j+gap] = temp;
            }
        }
    }
}

main()
{
    int a[] = {49, 38, 65, 97, 76, 13, 27, 49, 55, 4}, i;
    int size = sizeof(a) / sizeof(int);
    shell_sort(a, size);

    for (i = 0; i < size; i++)
        printf("%d ", a[i]);
}

各种排序算法总结



  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值