排序____

力扣

 O(n^2)=======================================

冒泡排序【稳定】⭐

 冒泡排序是最基本的排序算法,排序思路如下:
    1.一边比较一边向后两两交换,将最大值冒泡到最后一位。
    2.循环该过程n-1次(n为数组长度),数组此时为升序排列。

1.2 动图演示

1.3 代码实现 

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        //冒泡排序
        int n=nums.size();
        for(int i=0;i<n-1;i++)
        {
            for(int j=1;j<n-i;j++)
            {
                if(nums[j]<nums[j-1])
                {
                    int temp=nums[j];
                    nums[j]=nums[j-1];
                    nums[j-1]=temp;
                }
            }
        }
        return nums;
    }
};

插入排序【稳定】⭐

插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

3.1 算法描述

一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:

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

  假设一个数组,在其内部,数已经按照升序排列,此时有一个新的数a要加入数组,那么数组内大于a的数字需不断地向后腾出位置,直到a找到自己的位置,就可以将a插入该位置,此时原数组仍保持升序排列。
    同理,插入排序就是将已排序部分当成一个小数组,未排序部分将一个一个插入到小数组当中,循环插入,直至排序完成。

3.2 动图演示

3.2 代码实现

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        //插入排序
        int n=nums.size();
        for(int i=1;i<n;i++)
        {
            int flag=nums[i];
            int j=i-1;
            while(j>=0&&flag<nums[j])
            {
                nums[j+1]=nums[j];
                j--;
            }
            nums[j+1]=flag;
        }
        return nums;
    }
};

3.4 算法分析

插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

选择排序⭐

思想如下:
    1.找到数组未排序部分的最小值交换至数组未排序部分首位。
    2.与冒泡排序相同,循环该过程n-1次(n为数组长度),数组此时为升序排列。

2.2 动图演示

  

2.3 代码实现 

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        //选择排序
        int n=nums.size();
        for(int i=0;i<n-1;i++)
        {
            int min=i;
            for(int j=i+1;j<n;j++)
            {
                if(nums[min]>nums[j])
                    min=j;
            }
            if(min!=i)
                swap(nums[i],nums[min]);
        }
        return nums;
    }
};

2.4 算法分析

表现最稳定的排序算法之一,因为无论什么数据进去都是O(n2)的时间复杂度,所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。理论上讲,选择排序可能也是平时排序一般人想到的最多的排序方法了吧。

希尔排序

1959年Shell发明,第一个突破O(n2)的排序算法,是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序

希尔排序,即高级插入排序,是对插入排序的优化,思路如下:
    1.将一个长数组按照相同的间隔h分为多个小数组,每个小数组分别进行插入排序。
    2.将间隔h缩小,并继续排序,直至间隔为1。
    可以证明出当间隔h=3*h+1时,希尔排序平均时间复杂度最优,推理过程此处省略,有兴趣的读者可自行查询。

4.2 动图演示

4.3 代码实现

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        //希尔排序(高级插入排序)
        int n=nums.size();
        int h=1;
        while(h<n/3)
            h=3*h+1;
        while(h>0)
        {
            for(int i=h;i<n;i++)
            {
                int j=i-h;
                int flag=nums[i];
                while(j>=0&&flag<nums[j])
                {
                    nums[j+h]=nums[j];
                    j=j-h;
                }
                nums[j+h]=flag;
            }
            h=h/3;
        }
        return nums;
    }
};

 4.4 算法分析

希尔排序的核心在于间隔序列的设定。既可以提前设定好间隔序列,也可以动态的定义间隔序列。动态定义间隔序列的算法是《算法(第4版)》的合著者Robert Sedgewick提出的。 

O(nlogn)=======================================

快速排序⭐⭐

快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

6.1 算法描述

快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:

  • 从数列中挑出一个元素,称为 “基准”(pivot);
  • 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  • 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

6.2 动图演示

6.3 代码实现

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        quickSort(nums,0,nums.size()-1);
        return nums;
    }
private:
    void quickSort(vector<int>& nums,int left,int right){
        if(left<right){
            int standard=getStandard(nums,left,right);
            quickSort(nums,left,standard-1);
            quickSort(nums,standard+1,right);
        }
    }
    int getStandard(vector<int>& nums,int left,int right){
        int key=nums[left];
        while(left<right){
            while(left<right && nums[right]>=key){
                --right;
            }
            nums[left]=nums[right];
            while(left<right && nums[left]<=key){
                ++left;
            }
            nums[right]=nums[left];
        }
        nums[left]=key;
        return left;
    }    
};

快排存在的问题,如何优化:

3 种快排基准选择方法: 随机(rand函数)、固定(队首、队尾)、三数取中(队首、队中和队尾的中间数) 

4种优化方式: 

  • 优化1:当待排序序列的长度分割到一定大小后,使用插入排序
  • 优化2:在一次分割结束后,可以把与Key相等的元素聚在一起,继续下次分割时,不用再对与key相等 元素分割 
  • 优化3:优化递归操作 
  • 优化4:使用并行或多线程处理子序列

归并排序【稳定】⭐⭐

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。 

5.1 算法描述

  • 把长度为n的输入序列分成两个长度为n/2的子序列;
  • 对这两个子序列分别采用归并排序;
  • 将两个排序好的子序列合并成一个最终的排序序列。

5.2 动图演示

5.3 代码实现

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        //在排序前,建一个临时数组,避免递归频繁开辟空间
        vector<int>temp(nums.size());
        merge_sort(nums,0,nums.size()-1,temp);
        return nums;
    }
private:
    void merge_sort(vector<int> &nums,int left,int right,vector<int>temp){
        if(left<right){
            int mid = (left+right)/2;
            merge_sort(nums,left,mid,temp);//左边归并排序,使得左子序列有序
            merge_sort(nums,mid+1,right,temp);//右边归并排序,使得右子序列有序
            merge(nums,left,mid,right,temp);//将两个有序子数组合并操作
        }
    }
    void merge(vector<int> &nums,int left,int mid,int right,vector<int> temp){
        int i = left,j = mid+1;//左右序列指针
        int t = 0;//临时数组指针
        while (i<=mid && j<=right){
            if(nums[i]<=nums[j])
                temp[t++] = nums[i++];
            else
                temp[t++] = nums[j++];            
        }
        while(i<=mid)//将左边剩余元素填充进temp中
            temp[t++] = nums[i++];
        while(j<=right)//将右序列剩余元素填充进temp中
            temp[t++] = nums[j++];
        t = 0;        
        while(left <= right)//将temp中的元素全部拷贝到原数组中
            nums[left++] = temp[t++];
    }
};

5.4 算法分析

归并排序是一种稳定的排序方法。和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(nlogn)的时间复杂度。代价是需要额外的内存空间。

堆排序⭐⭐

力扣

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

7.1 算法描述

  • 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
  • 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
  • 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

7.2 动图演示

7.3 代码实现

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        vector<int> heap(nums.begin(), nums.begin()+k);
        buildHeap(heap);
        for(int i=k; i<nums.size(); ++i){
            if(heap[0]<nums[i]){
                heap[0] = nums[i];
                heapify(heap, 0);
            }            
        }
        return heap[0];
    }
private:
    void buildHeap(vector<int>& heap){
        int n = heap.size();
        for(int i=n/2-1; i>=0; --i){
            heapify(heap, i);
        }
    }

    void heapify(vector<int>& heap, int idx){
        int n = heap.size();
        int left = 2 * idx + 1;
        int right = 2 * idx + 2;
        int i  = idx;  // smallest
        if(left<n && heap[i]>heap[left]){
            i = left;
        }
        if(right<n && heap[i]>heap[right]){
            i = right;
        }
        if(i!=idx){
            std::swap(heap[i], heap[idx]);
            // recursively reorder affected subtrees
            heapify(heap, i);
        }
    }
   
};

O(n)========================================

基数排序【稳定】

基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。

10.1 算法描述

  • 取得数组中的最大数,并取得位数;
  • arr为原始数组,从最低位开始取每个位组成radix数组;
  • 对radix进行计数排序(利用计数排序适用于小范围数的特点);

10.2 动图演示

10.3 代码实现

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

var counter = [];

function radixSort(arr, maxDigit) {

    var mod = 10;

    var dev = 1;

    for (var i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {

        for(var j = 0; j < arr.length; j++) {

            var bucket = parseInt((arr[j] % mod) / dev);

            if(counter[bucket]==null) {

                counter[bucket] = [];

            }

            counter[bucket].push(arr[j]);

        }

        var pos = 0;

        for(var j = 0; j < counter.length; j++) {

            var value = null;

            if(counter[j]!=null) {

                while ((value = counter[j].shift()) != null) {

                      arr[pos++] = value;

                }

          }

        }

    }

    return arr;

}

10.4 算法分析

基数排序基于分别排序,分别收集,所以是稳定的。但基数排序的性能比桶排序要略差,每一次关键字的桶分配都需要O(n)的时间复杂度,而且分配之后得到新的关键字序列又需要O(n)的时间复杂度。假如待排数据可以分为d个关键字,则基数排序的时间复杂度将是O(d*2n) ,当然d要远远小于n,因此基本上还是线性级别的。

基数排序的空间复杂度为O(n+k),其中k为桶的数量。一般来说n>>k,因此额外空间需要大概n个左右。

桶排序

桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。

9.1 算法描述

  • 设置一个定量的数组当作空桶;
  • 遍历输入数据,并且把数据一个一个放到对应的桶里去;
  • 对每个不是空的桶进行排序;
  • 从不是空的桶里把排好序的数据拼接起来。 

9.2 图片演示

9.3 代码实现

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

function bucketSort(arr, bucketSize) {

    if (arr.length === 0) {

      return arr;

    }

    var i;

    var minValue = arr[0];

    var maxValue = arr[0];

    for (i = 1; i < arr.length; i++) {

      if (arr[i] < minValue) {

          minValue = arr[i];                // 输入数据的最小值

      else if (arr[i] > maxValue) {

          maxValue = arr[i];                // 输入数据的最大值

      }

    }

    // 桶的初始化

    var DEFAULT_BUCKET_SIZE = 5;            // 设置桶的默认数量为5

    bucketSize = bucketSize || DEFAULT_BUCKET_SIZE;

    var bucketCount = Math.floor((maxValue - minValue) / bucketSize) + 1;  

    var buckets = new Array(bucketCount);

    for (i = 0; i < buckets.length; i++) {

        buckets[i] = [];

    }

    // 利用映射函数将数据分配到各个桶中

    for (i = 0; i < arr.length; i++) {

        buckets[Math.floor((arr[i] - minValue) / bucketSize)].push(arr[i]);

    }

    arr.length = 0;

    for (i = 0; i < buckets.length; i++) {

        insertionSort(buckets[i]);                      // 对每个桶进行排序,这里使用了插入排序

        for (var j = 0; j < buckets[i].length; j++) {

            arr.push(buckets[i][j]);                     

        }

    }

    return arr;

}

9.4 算法分析

桶排序最好情况下使用线性时间O(n),桶排序的时间复杂度,取决与对各个桶之间数据进行排序的时间复杂度,因为其它部分的时间复杂度都为O(n)。很显然,桶划分的越小,各个桶之间的数据越少,排序所用的时间也会越少。但相应的空间消耗就会增大。 

计数排序

计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。

8.1 算法描述

  • 找出待排序的数组中最大和最小的元素;
  • 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
  • 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
  • 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。

8.2 动图演示

8.3 代码实现

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

function countingSort(arr, maxValue) {

    var bucket = new Array(maxValue + 1),

        sortedIndex = 0;

        arrLen = arr.length,

        bucketLen = maxValue + 1;

    for (var i = 0; i < arrLen; i++) {

        if (!bucket[arr[i]]) {

            bucket[arr[i]] = 0;

        }

        bucket[arr[i]]++;

    }

    for (var j = 0; j < bucketLen; j++) {

        while(bucket[j] > 0) {

            arr[sortedIndex++] = j;

            bucket[j]--;

        }

    }

    return arr;

}

8.4 算法分析

计数排序是一个稳定的排序算法。当输入的元素是 n 个 0到 k 之间的整数时,时间复杂度是O(n+k),空间复杂度也是O(n+k),其排序速度快于任何比较排序算法。当k不是很大并且序列比较集中时,计数排序是一个很有效的排序算法。

外部排序⭐=================================

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值