基本排序算法总结

1.插入排序(insertion_sort)O(n2)

对少量数据排序很有效,不需要额外的存储空间。待排序列已经是从小到大,最坏就是逆序的时候了。且是稳定的。

#include <stdio.h>
#include <assert.h>

int exchange(int *a, int i, int j);
int insert(int *a, int s, int e);

int main(int argc, char *argv[])
{
        int a[] = {4, 2, 5, 1, 3};
        insert(a, 0, 4);
        int i;
        for(i=0; i<5; i++)
                printf("%d\n", a[i]);
}

int exchange(int *a, int i, int j)
{
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
        return 0;
}

int insert(int *a, int s, int e)
{
        int i;
        for(i=s+1; i<e+1; i++)
        {
                int key = a[i];//这里的key是必须保存的,不能为了省一个变量,key用a[i]代替,因为第一次a[j+1]=a[j]时候,a[i](a[j+1])的值被覆盖了
                int j=i-1;
                while(j>=0 && a[j]>key)
                {
                        a[j+1] = a[j];
                        j--;
                }
                a[++j] = key;//这里的++很重要,可以测试当正好有序时,while条件不成立,必须是自己的值赋给自己,key为a[i],所以a[++j]也必须为a[i]
        }
}

2.归并排序(merge_sort) O(nlogn) 还是稳定的,这个在时间复杂度为O(nlogn)不常见

需要用到递归和分治(两种方法经常结合起来使用来形成高效的算法),推导时间复杂度用到了主定理这个牛x的定理。

#include<iostream>
using namespace std;

void merge_sort(int *a, int s, int e);
void merge(int *a, int s, int m, int e);

int main()
{
        int a[] = {2, 4, 1, 9, 7, 8, 10, 3, 5, 6};
        merge_sort(a, 0, 9);
        for(int i=0; i<10; i++)
                printf("%d\n", a[i]);
        return 0;
}

void merge_sort(int *a, int s, int e)
{
        if(s<e)
        {
                int m = s + (e - s)/2;
                merge_sort(a, s, m);
                merge_sort(a, m+1, e);
                merge(a, s, m, e);
        }
}

void merge(int *a, int s, int m, int e)
{
        int left_len = m-s+1;
        int right_len = e-m;
        int *left = (int *)malloc(left_len*sizeof(int));
        int *right = (int *)malloc(right_len*sizeof(int));

        for(int i=0; i<left_len; i++)
                left[i] = a[i+s];
        for(int j=0; j<right_len; j++)
                right[j] = a[j+m+1];
        int p = 0;
        int q = 0;
        int k = s;
        while(p<left_len && q<right_len)
        {
                if(left[p] < right[q])
                        a[k++] = left[p++];
                else
                        a[k++] = right[q++];
        }
        if(p == left_len)
                while(q < right_len)
                        a[k++] = right[q++];
        if(q == right_len)
                while(p < left_len)
                        a[k++] = left[p++];

        free(left);
        free(right);
}

 

3.冒泡排序O(n2)

稳定,代码容易敲。

#include <stdio.h>
#include <assert.h>

int exchange(int *a, int i, int j);
int maopao(int *a, int s, int e);
int main(int argc, char *argv[])
{
        int a[] = {4, 2, 5, 1, 3};
        maopao(a, 0, 4);
        int i;
        for(i=0; i<5; i++)
                printf("%d\n", a[i]);
}

int maopao(int *a, int s, int e)
{
        int len = e-s+1;
        int i;
        int j;
        for(j=len-1; j>0; j--)
                for(i=s; i<j+s; i++)
                        if(a[i]>a[i+1])
                                exchange(a, i, i+1);
        return 0;
}

int exchange(int *a, int i, int j)
{
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
        return 0;
}

4.堆排序O(nlogn)

不稳定。但是时间复杂度很稳定,不管什么情况都是nlogn,可以确切知道在什么时间范围内。有特殊的应用像什么优先级队列,前多少大的问题。

#include <stdio.h>
#include <iostream>
using namespace std;

int build_max_heap(int *a, int heap_size);  //这里heap_size理解为下标的最大值加一,即为堆里面元素的个数
int heap_sort(int *a, int len);  //这里排序的时候,数组a的下标必须是从0开始,否则不会满足父结点和左右孩子结点的关系
int max_heapify(int *a, int i, int heap_size);  //调整使得a[i]为父节点的堆为大顶堆
int exchange(int *a, int i, int j);

int main(int argc, char *argv[])
{
        int a[] = {1, 3, 5, 2, 4};
        int len = 5;
        heap_sort(a, 5);
        int i;
        for(i=0; i<5; i++)
                printf("%d\n", a[i]);
        return 0;
}

int exchange(int *a, int i, int j)
{
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
        return 0;
}

int max_heapify(int *a, int i, int heap_size)
{
        int largest = i;
        if(2*i+1 <= heap_size-1 && a[2*i+1] > a[i]) //这里,<=非常容易写成>=!!
                largest = 2*i+1;
        if(2*i+2 <= heap_size-1 && a[2*i+2] > a[largest])
                largest = 2*i+2;

        if(largest != i)
        {
                exchange(a, i, largest);   //这一句不要忘了,在递归前交换
                max_heapify(a, largest, heap_size);
        }
        return 0;
}
int build_max_heap(int *a, int heap_size)
{
        int i;
        for(i = heap_size/2; i>=0; i--)
                max_heapify(a, i, heap_size);
        return 0;
}

int heap_sort(int *a, int len)
{
        build_max_heap(a, len);
        int i;
        for(i= len-1; i>0; i--)
        {
                exchange(a, 0, i);
                max_heapify(a, 0, i);  //这里的heap_size参数是变化的
        }
}

 

5.快排O(nlogn)

用的最多了,虽然与堆排序时间复杂度一样,但大多数情况下,前面的常数较小,比堆排序多数情况下速度快。

#include <stdio.h>
#include <assert.h>

void quickSort(int *a, int s, int e);
int partition(int *a, int s, int e);
void exchange(int *a, int i, int j);

int main(int argc, char *argv[])
{
        int a[] = {1, 3, 4, 2, 5};
        quickSort(a, 0, 4);
        int i;
        for(i=0; i<5; i++)
                printf("%d\n", a[i]);
        return 0;
}

void exchange(int *a, int i, int j)
{
        assert(a != NULL);
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
}

void quickSort(int *a, int s, int e)
{
        if(s < e)
        {
                int temp = partition(a, s, e);
                quickSort(a, s, temp-1); //这里,是递归调用quickSort,有时候写成partition
                quickSort(a, temp+1, e);
        }
}

int partition(int *a, int s, int e)
{
        int key = a[e];
        int i = s - 1;  //注意i的开始位置,很容易写成i = s。可以想象一种极端的情况,s到e-1的位置上都比e位置的大,如果i为s结果就错了
        int j;
        for(j=s; j<e; j++)
        {
                if(a[j]<key)
                {
                        i++;
                        exchange(a, i, j);
                }  //有时候在if后面会多写一行j++,忘记for循环里面的++
        }
        i++;
        exchange(a, i, e);
        return i;
}

 

6.计数排序

对数据有要求,数据必须知道数据的范围,假设每个数都在0~k之间,当k=O(n)时侯,计数排序的运行时间为O(n),空间复杂度O(n)。为稳定的,对数据有特定的需求,例如当对年龄排序的时候就特别的合适。(与上面的排序算法有本质的不同,不需要比较)

 

7.基数排序

n个d位数,每一位有k中可能的取值,如果所用的稳定排序需要O(n+k)时间,基数排序能以O(d(n+k))的时间对这些数排序。

8.桶排序

桶排序
这个排序算法其实还是有很多应用的,桶排序算法假设输入由一个随机过程产生,该过程将元素均匀而独立额分布在一个区间上。像实际应用中,并不是严格的要求均匀分布,大量学生成绩,年龄这些有范围的数据,都可以尝试用桶排序来,最佳的时间复杂度为O(n),最差的为O(n2)完全在一个桶内。一般用一个链表来表示一个桶,空间复杂度低为O(n),否则用数组的话空间复杂度要O(n*k),k为桶的个数。

算法步骤也简单:
1.根据数据特征划分桶
2.把数据输入到各个桶中
3.各个桶排序(一般2,3两步可以一起用插入排序做了)
4.把各个桶元素按照顺序列出来

 

#include <iostream>
using namespace std;

typedef struct element
{
        int value;
        struct element *next;
}Element;

void bucket_sort(int *values, int s, int e);

int main()
{
        int values[] = {23, 12, 34, 55, 66, 43, 67, 89, 90, 11, 20, 30, 40, 55, 99, 33, 22, 77, 44, 11};
        bucket_sort(values, 0, 19);
        for(int i=0; i<20; i++)
                printf("%d\t", values[i]);
        printf("\n");
        return 0;
}

void bucket_sort(int *values, int s, int e)
{
        int low_end = 0;
        int high_end = 100; //不能等于100
        int interval = 10;
        int bucket_num = (high_end - low_end)/interval;
        Element **bucket = (Element **)calloc(bucket_num, sizeof(Element *));

        //-----把数据放入桶并排序-----
        for(int i=s; i<=e; i++)
        {
                Element *temp = bucket[values[i]/interval];
                Element *insert = (Element *)malloc(sizeof(Element));
                insert->value = values[i];

                if(temp == NULL) //桶内没有元素
                {
                        bucket[values[i]/interval] = insert;
                        insert->next = NULL;
               }
                else //桶内有元素
                {
                        Element *pre = NULL;
                        while(temp != NULL)
                        {
                                if(temp->value > values[i])
                                        break;
                                pre = temp;
                                temp = temp->next;
                        }
                        //while结束后有三种情况,一是temp为空,元素最大应插入桶末尾,二是找到比待插入元素大的值,pre不为空,元素应插入到pre后面。三是当pre为NULL时,插入到桶开头
                        if(temp == NULL)
                        {
                                //插入到桶末尾
                                pre->next = insert;
                                insert->next = NULL;

                        }
                        else
                        {
                                if(pre == NULL)
                                {
                                        //插入到桶开头
                                         bucket[values[i]/interval] = insert;
                                         insert->next = temp;
                                }
                                else
                                {
                                        //插入到桶的中间
                                        pre->next = insert;
                                        insert->next = temp;
                                }
                        }
                }
        }
        //-----排序后输入到values中-----
        int j = 0;
        for(int i=0; i<bucket_num; i++)
        {
                Element *temp = bucket[i];
                while(temp)
                {
                        values[j++] = temp->value;
                        temp = temp->next;
                }
        }
}


 

稳定性总结,选择排序、快速排序、希尔排序、堆排序不是稳定的排序算法,而冒泡排序、插入排序、归并排序和基数排序是稳定的排序算法。选择算法时不能光看时间复杂度,要结合数字的特征。一般情况下基数排序时间复杂度为O(n),看上取要比快速排序的O(nlgn)要好,然而,在两个时间中隐含在O记号中的常数因子是不同的,尽管基数排序执行的变数要比快排少,但是每一遍所需的时间都要长的多。哪个排序方法好取决于底层机器的实现(快排通常比基数排序更为有效的利用硬件缓存),同时还取决于输入的数据。另外利用计数排序作为中间稳定排序的基数排序不是原地排序,而很多O(nlgn)的比较排序算法则可以做到原地排序。因此当内存资源容量比较宝贵的时候,像快排这样的原地排序算法可能是更为可取的。

你可能感兴趣的:(基本排序算法总结)