数据结构——排序算法

156 阅读2分钟

排序算法:

分类:

冒泡排序、选择排序、插入排序、归并排序、计数排序(counting sort)、基数排序(radix sort)、希尔排序、堆排序、桶排序.

简单排序: 冒泡排序 - 选择排序 - 插入排序

高级排序: 归并排序 - 快速排序

排序的简单算法的主要操作:

  • 比较两个数据项.
  • 交换两个数据项, 或者复制其中一项.
  • 但是, 每种算法具体实现的细节有所不同.

创建列表:

创建一个列表封装我们的数据项

    class ArrayList{
       constructor(){
       //创建列表
        this.array=[]
       }
    //    向数组中添加数据
       insert(ele){
        this.array.push(ele)
       }
    //    将数组中的元素用字符串连接起来
       tostring(){
        return this.array.join()
       }
    }

简单排序:

1.冒泡排序:

定义: 冒泡排序算法相对其他排序运行效率较低, 但是在概念上它是排序算法中最简单的。

代码:

         //    冒泡排序
    bubbleSort(){
        // 控制趟数    控制次数
        for(let i=0;i<this.array.length-1;i++){
            // console.log(i,"趟数")
            for(let j=0;j<this.array.length-1;j++){
                // 比较
                if(this.array[j+1]<this.array[j]){
                    this.swap(j,j+1)
                }
            }
        }
    }
    // 交换两个位置的值
    swap(m,n){
        // 交换两个位置的值
        let temp = this.array[n]
        this.array[n] = this.array[m]
        this.array[m] = temp
    }

冒泡排序的思路:

相邻元素进行比较,如果前一个小于后一个元素,就相互交换位置

  • 对未排序的各元素从头到尾依次比较相邻的两个元素大小关系
  • 如果左边的元素大, 则两元素交换位置
  • 向右移动一个位置, 比较后面两个元素
  • 当走到最右端时, 最大的元素一定被放在了最右边
  • 按照这个思路, 从最左端重新开始, 这次走到倒数第二个位置的元素即可.
  • 依次类推, 就可以将数据排序完成

图解:

image.png

每趟排序的过程如下:

image.png

2.选择排序:

定义:选择排序改进了冒泡排序, 将交换的次数由O(N²)减少到O(N), 但是比较的次数依然是O(N²)

代码:

 selecttionSort(){
        for(let i=0;i<this.array.length-1;i++){
            let min=i;
            for(let j=i+1;j<this.array.length;j++){
                
                if(this.array[j]<this.array[min]){
                    min=j
                }
                this.swap(j,min)
            }
        }
    }

选择排序的思路

在每一趟中找出最小的元素,放在第一个索引的位置

  • 选定第一个索引位置,然后和后面元素依次比较
  • 如果后面的元素, 小于第一个索引位置的元素, 则交换位置
  • 经过一轮的比较后, 可以确定第一个位置是最小的
  • 然后使用同样的方法把剩下的元素逐个比较即可
  • 可以看出选择排序,第一轮会选出最小值,第二轮会选出第二小的值,直到最后

图解:

image.png

每趟排序的过程如下:

image.png

3.插入排序:

定义:插入排序是简单排序中效率最好的一种.

插入排序也是学习其他高级排序的基础, 比如希尔排序/快速排序, 所以也非常重要.

代码:

// 插入排序
    insertionSort(){
        for(let i=1;i<this.array.length;i++){
            let j=i
            let mark=this.array[i]
            while(this.array[j-1]<mark&&j>0){
                    this.array[j]=this.array[j-1]
                    j--;
                }
                this.array[j]=mark
            }
            
        }  

插入排序的思路:

从1开始,标记一个值,默认标记的值前面的元素都是已经排好序的,将标记的值开始逐渐与它的上一个元素相比较,如果小于上一个元素,就交换位置,并且继续与上一个元素相比较,直到前面已经没有元素可以比较;如果大于上一个元素,不交换位置,且停止与上一个元素比较

  • 局部有序:

    • 插入排序思想的核心是局部有序. 什么是局部有序呢?
    • 比如在一个队列中的人, 我们选择其中一个作为标记的队员. 这个被标记的队员左边的所有队员已经是局部有序的.
    • 这意味着, 有一部门人是按顺序排列好的. 有一部分还没有顺序.
  • 插入排序的思路:

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

图解:

image.png

高级排序:

1.归并排序:

代码:

   // 归并排序
    mergerSort(){
        this.array=this.divide(this.array)
    }
    // 将数组分开
    divide(arr){
        if(arr.length<=1){
            return arr
        }else{
            let index=Math.floor(arr.length/2)
            let leftarr=arr.slice(0,index)
            let rightarr=arr.slice(index)
            return this.merge(this.divide(leftarr),this.divide(rightarr))
        }
    }
    // 将所有的数组合起来
    merge(left,right){
        let newarr=[]
        while(left.length&&right.length){
            if(left[0]<right[0]){
                newarr.push(left.shift())
            }else{
                newarr(right.shift())
            }
        }
        return newarr.concat(left,right)
    }

基本思想与过程:

先递归的分解数列再合并数列(分治思想的典型应用)

  (1)将一个数组拆成A、B两个小组,两个小组继续拆,直到每个小组只有一个元素为止。

  (2)按照拆分过程逐步合并小组,由于各小组初始只有一个元素,可以看做小组内部是有序的,合并小组可以被看做是合并两个有序数组的过程。

  (3)对左右两个小数列重复第二步,直至各区间只有1个数。

  下面对数组【42,20,17,13,28,14,23,15】进行归并排序,模拟排序过程如下:

  第一步:拆分数组,一共需要拆分三次(logN);

    第一次拆成【42,20,17,13】,【28,14,23,15】,

    第二次拆成【42,20】,【17,13】,【28,14】,【23,15】,、

    第三次拆成【42】,【20】,【17】,【13】,【28】,【14】,【23】,【15】;

  第二步:逐步归并数组,采用合并两个有序数组的方法,每一步其算法复杂度基本接近于O(N)

    第一次归并为【20,42】,【13,17】,【14,28】,【15,23】

    第二次归并为【13,17,20,42】,【14,15,23,28】,

    第三次归并为【13, 14, 15, 17, 20, 23, 28, 42】

图解:

image.png

2.快速排序:

快速排序几乎可以说是目前所有排序算法中, 最快的一种排序算法.

当然, 没有任何一种算法是在任意情况下都是最优的, 比如希尔排序确实在某些情况下可能好于快速排序. 但是大多数情况下, 快速排序还是比较好的选择.

快速排序是什么?

  • 希尔排序相当于插入排序的升级版, 快速排序其实是我们学习过的最慢的冒泡排序的升级版.
  • 我们知道冒泡排序需要经过很多次交换, 才能在一次循环中, 将最大值放在正确的位置.
  • 而快速排序可以在一次循环中(其实是递归调用)找出某个元素的正确位置, 并且该元素之后不需要任何移动.

代码

 // 快速排序
    quickSort(){
        this.array=this.quick(this.array)
    }
    quick(arr){
        if(arr.length<=1){
            return arr
        }else{
            // 二分法
            let index=Math.floor(arr.length/2);
            let middle=arr.splice(index,1)[0]

            let leftarr=[]
            let rightarr=[]
            for(let i=0;i<arr.length;i++){
                if(arr[i]<middle){
                    leftarr.push(arr[i])
                }else{
                    rightarr.push(arr[i])
                }
            }
            // console.log(leftarr,rightarr)
            return this.quick(leftarr).concat(middle,this.quick(rightarr))
        }

    }

快速排序的思想:

从数组中间取一个值,将数组中小于这个值的元素放在这个值左边的数组中,大于这个值的元素放在右边的数组中。再在这两个数组继续进行上面的操作,直到数组中只有一个元素为止停止操作。

  • 快速排序最重要的思想是分而治之.
  • 比如我们下面有这样一顿数字需要排序:
    • 第一步: 从其中选出了65. (其实可以是选出任意的数字, 我们以65举个栗子)
    • 第二步: 我们通过算法: 将所有小于65的数字放在65的左边, 将所有大于65的数字放在65的右边.
    • 第三步: 递归的处理左边的数据.(比如你选择31来处理左侧), 递归的处理右边的数据.(比如选择75来处理右侧, 当然选择81可能更合适)
    • 最终: 排序完成

图解:

image.png

查找算法(有两种):

     // 1.搜索算法
    shunxuSerach(ele){
        for(let i=0;i<this.item.length;i++){
            if(this.item[i]==ele){
                return true
            }
        }
        return  false
    }
    // 2.二分法查找,必须有序
    BinarySearch(ele){
        let start=0;
        let end=this.item.length-1;
        if(ele<this.item[start]||ele>this.item[end]){
            return flase
        }
        while(start<=end){
            let middle=Math.floor((start+end)/2)
            if(ele<this.item[middle]){
                end=middle-1
            }else if(ele>this.item[middle]){
                start=middle+1
            }else{
                return true
            }
        }
        return false
    }