码迷,mamicode.com
首页 > 编程语言 > 详细

十大经典排序算法(打倒拖延症系列)

时间:2019-01-29 14:52:54      阅读:163      评论:0      收藏:0      [点我收藏+]

标签:one   交换   aik   序列   增量排序   选择   归并   quic   sorted   

一个排序算法可视化的软件,很魔性。

链接:https://pan.baidu.com/s/1hCoMku7UL7IN4hIdYTsuJg  提取码:4y4v 


   1.冒泡排序 

技术分享图片
void bubbleSort(int *a, int n) {
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n - i; j++) {
            if (a[j - 1] > a[j])
                swap(a[j - 1], a[j]);
        }
    }
}
View Code

  2.选择排序

技术分享图片
void selectSort(int *a, int length) {
    for (int i = 0; i < length; i ++) {
        int minIndex = i;
        for (int j = i + 1; j < length; j++) {
            minIndex = a[j] < a[minIndex] ? j : minIndex;
        }
        swap(a[i], a[minIndex]);
    }
}
View Code

  3.插入排序

插入排序在数组近乎于有序的时候,比O(nlogn)的排序是还要快。

技术分享图片
void insertSort(int *a, int length) {
    for (int i = 1; i < length; i++) {
        int j, temp = a[i];
        for (j = i; temp < a[j-1] && j>0; j--) {
            a[j] = a[j-1];
        }
        a[j] = temp;
    }
}
View Code

  4.希尔排序(特殊插入排序)

间隔序列的取法有各种方案。最初shell提出取t/2向下取整,直到t=1。但由于直到最后一步,在奇数位置的元素才会与偶数位置的元素进行比较,这样使用这个序列的效率会很低。不同的序列会使希尔排序算法的性能有很大的差异 至今为止还没有一个最完美的增量序列公式,这里选用全为t/2向下取整

  希尔排序必须保证最后一次划分间隔为1,希尔排序又叫缩小增量排序

技术分享图片

(这里盗用数据结构书上的图了~,可以说是很形象了,不过图中选取的增量序列是  t - 2)

void shellSort(int*data, unsigned int len)
{
    for (int div = len / 2; div >= 1; div = div / 2) {
        for (int i = 0; i < div; ++i) {
            for (int j = i + div; j < len; j += div) {
                int k, temp = data[j];
                for (k = j - div; k >= 0 && temp < data[k]; k -= div) {
                    data[k + div] = data[k];
                }
                data[k + div] = temp;
                /*for (k = j; k >= div && temp < data[k - div]; k -= div) {
                    data[k] = data[k - div];
                }
                data[k] = temp;*/
            }
        }
    }
}

  5. 归并排序

   该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序利用了完全二叉树特性,是一种十分高效的稳定排序。

技术分享图片

void meger(int *a, int left, int mid, int right) {
    int *b = new int[right - left + 1];
    int t = 0, i = left, j = mid + 1;

    while (i <= mid && j <= right) { 
        b[t++] = a[i] < a[j] ? a[i++] : a[j++]; 
    }

    while (i <= mid) b[t++] = a[i++];
    while (j <= right) b[t++] = a[j++];

    i = left; t = 0;
    while (i <= right) a[i++] = b[t++];
    delete[]b;
}
void megerSort(int *a, int left, int right) {
    if (left == right) return;
    int mid = (right + left) >> 1;
    megerSort(a, left, mid);
    megerSort(a, mid + 1, right);
    meger(a, left, mid, right);
}

   6.快速排序(partition)

  快速排序的思想很简单,就是先确定一个基准 SIGN,左右开工,从两边遍历数组将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。

技术分享图片

    快速排序选取第一个元素作为基准。这样在数组已经有序的情况下,算法将会降级为O(n^2)。一种比较常见的优化方法是随机化算法,即随机选取一个元素作为基准。

    冯·诺依曼·赵四说过:“快速排序是看脸的,人丑排得慢。” 看来我是排不好了。

//low:first index;high:last index
void quickSortTest(int *a,int low,int high) {
    if (low >= high)return;
    //这里i要等于low,是为了j从hign一直到low都没找到比Sign小的值,就不需要进行交换
    int i = low, j = high;
    int sign = a[low];
    while (i < j) {
  //因为基准值是a[0],所以这里先j--,确保最后换的时候,a[j]是小于等于a[0]的。如果基准值是a[length-1],就是i++了。
        while (a[j] >= sign && i < j)j--;
        while (a[i] <= sign && i < j)i++;
        swap(a[i], a[j]);
    }
    swap(a[low], a[i]);
    quickSortTest(a, low, i - 1);
    quickSortTest(a, i + 1, high);
}

 随机选取主元的快速排序:

 

void partition(int *a, int left, int right) {
    if (left >= right) return;
    //获取left到right中的随机值
    int sign = rand() % (right - left) + left;
    swap(a[left], a[sign]);
    int i = left, j = right;
    while (i < j) {
        while (a[j] >= a[left] && i < j)j--;
        while (a[i] <= a[left] && i < j)i++;
        swap(a[i], a[j]);
    }
    swap(a[left], a[i]);
    partition(a, left, i - 1);
    partition(a, i + 1, right);
}
void RandomQuickSort(int *a,int length) {
    partition(a, 0, length - 1);
}

 

  7.基数排序

  刚刚看到基数排序原理的时候,老夫脑子里只有一句话:“卧槽??”,很巧妙,而且很简单的思想。不难推出,基数排序适合于处理数据范围小的数据。

   技术分享图片

  基数排序属于很好懂,实现起来,很多小地方,比较繁琐。

  重要的是思想,为了缩短代码量,这里借助STL vector 实现了LSD(我怎么这么短系列):

void radixSort(int *a,int length) {
    //开19个,是为了处理负数,-9 --》9
    vector<vector<int>>radix(19);
    int max = a[0], count = 1, div = 1;
    //求最大值
    for (int i = 1; i < length; i++) 
        max = max > a[i] ? max : a[i];
    //求最大位数
    while (max /=10 ) count++;
    while(count--) {
        for (int i = 0; i < length; i++) {
                int index = a[i] < 0 ? -a[i] / div % 10 : a[i] / div % 10 + 9;
                radix[index].push_back(a[i]);
        }
        for (int i = 0, j = 0; i < length; j++ ) {
            int k = 0;
            while (k < radix[j].size()) a[i++] = radix[j][k++];
                radix[j].clear();
        }
        div *= 10;
    }
}

   这个是根据百科里的算法改的(百科里的代码竟然没考虑负数的???)

  因为把数据装桶的时候,是逆序装的,这里index直接用 a[j] / div % 10 + 9; 就行了

int MaxBit(int a[], int length) 
{
    int max = a[0], count = 1, div = 1;
    for (int i = 1; i < length; i++)
        max = max > a[i] ? max : a[i];
    while (max /= 10) count++;
    return count;
}
void radixsort1(int a[], int length) //基数排序
{
    int i,j,k,bit = MaxBit(a, length);
    int *tmp = new int[length];
    int *count = new int[19]; //计数器
    int div = 1;
    for (i = 1; i <= bit; i++){
        memset(count, 0, sizeof(int) * 19);
        for (j = 0; j < length; j++) {
            int index = a[j] / div % 10 + 9;
            count[index]++;
        }
            
        for (j = 1; j < 19; j++)
            count[j] += count[j - 1];
        //后面碰到的要往后放,tmp数组是倒过来的往里存的,这里必须逆序
        for (j = length - 1; j >= 0; j--) {
            int index = a[j] / div % 10 + 9;
            tmp[count[index]-- - 1] = a[j];
        }
        for (j = 0; j < length; j++)
            a[j] = tmp[j];
        div *= 10;
    }
    delete[]tmp;
    delete[]count;
}

   8.计数排序

  假定20个随机整数的值如下:

  9,3,5,4,9,1,2,7,8,1,3,6,5,3,4,0,10,9 ,7,9

遍历随机数列,每一个整数按照其值对号入座,对应数组下标的元素进行加1操作。

遍历完毕时,数组的状态如下:

技术分享图片

直接遍历数组,输出数组元素的下标值,元素的值是几,就输出几次:

0,1,1,2,3,3,3,4,4,5,5,6,7,7,8,9,9,9,9,10

朴素版:

技术分享图片
void countSort(int *a, int length) {
    int min = getMin(a, length);
    int max = getMax(a, length);
    int *count = new int[max - min + 1];
    memset(count, 0, sizeof(int)*(max - min + 1));
    for (int i = 0; i < length; i++) {
        count[a[i] - min]++;
    }
    for (int i = 0, j = 0; j < max - min + 1; j++) {
        if (count[j] != 0) {
            while (count[j]--) a[i++] = j + min;
        }
    }
}
View Code

  正版:

void countSort(int* a,int length) {
    int max = getMax(a,length);
    int min = getMin(a, length);
    int offset = max - min;
    int* countArray = new int[offset + 1];
    memset(countArray, 0, sizeof(int)*(offset + 1));
    for (int i = 0; i < length; i++) {
        countArray[a[i] - min]++;
    }
    for (int i = 1; i < offset + 1; i++) {
        countArray[i] += countArray[i-1];
    } 
    int* sortedArray = new int[length];
    for (int i = length - 1; i >= 0; i--) {
        //把a[i]放在该放的位置上
        sortedArray[countArray[a[i] - min] - 1] = a[i];
        countArray[a[i] - min]--;
    }
    for (int i = 0; i < length; i++)
        a[i] = sortedArray[i];
}

1.当数列最大最小值差距过大时,并不适用计数排序。

比如给定20个随机整数,范围在0到1亿之间,这时候如果使用计数排序,需要创建长度1亿的数组。不但严重浪费空间,而且时间复杂度也随之升高。

2.当数列元素不是整数,并不适用计数排序。

如果数列中的元素都是小数,比如25.213,或是0.00000001这样子,则无法创建对应的统计数组。这样显然无法进行计数排序。


 9.桶排序

上面说了,当数据范围较大、元素不是整数是不适合或不能使用计数排序,桶排序是计数排序的升级版,不属于比较排序,也不受O(n*logn)下限的影响。

更快的排序,总是以空间为代价的。

第一步:

首先确定有多少个桶,这里选取length个桶,每个桶的区间跨度为(max - min)/(length - 1)(具体建立多少个桶,如何确定桶的区间范围,有很多不同的方式。)

第二步:

遍历原始数列,把元素对号入座放入各个桶中:

技术分享图片

第三步:

每个桶内部的元素分别排序,借助其他简单排序方法。

void bucketSort(int *a, int length) {
    int min = getMin(a, length);
    int max = getMax(a, length);
    double div = (max - min) / (double)(length - 1);
    vector<vector<int>>bucket(length);
    for (int i = 0; i < length; i++) {
        bucket[floor((a[i] - min) / div)].push_back(a[i]);
    }
    for (int i = 0; i < length; i++) {
        //对每个桶 进行插入排序
        for (int j = 1; j < bucket[i].size(); j++) {
            int k,sign = bucket[i][j];
            for (k = j; k > 0 && sign < bucket[i][k - 1]; k--) {
                bucket[i][k] = bucket[i][k - 1];
            }
            bucket[i][k] = sign;
        }
    }
    for (int i = 0, k = 0; i < length; i++) {
        for (int j = 0; j < bucket[i].size(); j++) {
            a[k++] = bucket[i][j];
        }
        bucket[i].clear();
    }
}

  桶排序克服了计数排序上述的两个问题,但桶排序希望数据是均匀分布的,否则,所有数据全都进了一个桶,就退化成你使用的桶内排序方法了。


 十、堆排序

  想实现堆排序,必须要先了解二叉堆,堆排序就是,就是循环移除顶部元素到数组末尾,然后 自上而下 Sink重建堆的操作。

堆排序可以分为两步:

1)根据初始数组去构造一个最大堆

从最后一个父节点开始往上,用 Sink 调整最大堆,堆顶数组为最大元素。

2)每次交换第一个和最后一个元素,length--,然后把剩下元素重新调整为最大堆。 

这里需要知道几点:

数组下标是从0开始的,对于构建出来的二叉堆,第k个元素的左孩子就是2*k+1,右孩子是2*k+2

最大堆要求,所有子节点不大于堆顶,所以是可以处理存在重复数字的数组的。

void Sink(int *a, int index,int length) {
//如果元素有左孩子
while (index * 2 + 1 < length) { int left = index * 2 + 1; if (left + 1 <= length - 1 && a[left] < a[left + 1]) left++; if (a[index] > a[left])break; swap(a[index], a[left]); index = left; } } void heapSort(int *a,int length) { for (int i = length / 2 - 1; i >= 0; i--) Sink(a, i, length); while(length--) { swap(a[0], a[length]); Sink(a, 0, length); } }

十大经典排序算法(打倒拖延症系列)

标签:one   交换   aik   序列   增量排序   选择   归并   quic   sorted   

原文地址:https://www.cnblogs.com/czc1999/p/10266436.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!