【数据结构与算法】第六章 排序(数组)

第六章 排序(数组)

 

1、我们这章所说的排序是针对数组的排序。切记这一点。

2、被排序的对象必须属于Comparable类型,是可比较的。

https://www.cnblogs.com/onepixel/articles/7674659.html

 

我们介绍以下几种排序的方式:

选择排序、插入排序、归并排序、快速排序、堆排序

 

一、选择排序(O(n^2))

选择排序也是一种简单的排序方式。工作原理:从未排序的元素中找到最小值放到排序的起始位置。然后再从剩余未排序的元素中找到最小值,放到已排序的末尾。

1、算法描述

    1)循环所有未排序的节点

    2)找到未排序节点最小值的索引

    3)将最小值的索引与未排序的首位置进行值交换。

2、动画演示

3、代码实现

public void sort(T[] arr) {

    int size = arr.length;

    for (int i = 0; i < size; i++) {

        int minIndex = i;

        for (int j = i + 1; j < size; j++) {

            if (arr[j].compareTo(arr[minIndex]) < 0) {

                minIndex = j;

            }

        }

        swap(arr, i, minIndex);

    }

}

private void swap(T[] arr, int i, int j) {

    T tmp = arr[i];

    arr[i] = arr[j];

    arr[j] = tmp;

}

 

二、插入排序(O(n^2))

插入排序是一种比较直观的排序算法。它的工作原理是通过构建有序序列,对于未排序的数据,在已排序的序列中从后向前扫描进行比较,直到找到相应的合适位置进行插入。

1、算法描述:

    1)循环所有的元素

    2)得到未排序的首元素位置

    3)依次与它前一个位置进行比较,如果它小于它前一个位置的值,那么进行交换.

 

2、动画演示:

3、代码实现:

public void sort(T[] arr) {

    int size = arr.length;

    for (int i = 1; i < size; i++) {

        for (int j = i; j > 0; j--) {

            if (arr[j].compareTo(arr[j - 1]) < 0) {

                swap(arr, j - 1, j);    //交易它和它前面的值

            } else {

                break;

            }

        }

    }

}

 

 

三、归并排序(O(nlogN))

1、算法描述:

1)先逐层分成一半,直至只有一个元素,到底

2)逐层向上进行归妆

最后得到的结果就是有序的:

3)这个实现过程我们需要额外的空间帮助我们进行归并。(缺点,空间复杂度高)

依靠三个指针同时进行操作:

 

2、动画演示:

3、代码实现:

public class MergeSort {

    public static void sort(Comparable[] arr) {

        sort(arr, 0, arr.length - 1);

    }

 

    private static void sort(Comparable[] arr, int start, int end) {

        if (start >= end) {

            return;

        }

        int mid = (start + end) / 2;

        sort(arr, start, mid);

        sort(arr, mid + 1, end);

        merge(arr, start, mid, end);

    }

 

    private static void merge(Comparable[] arr, int l, int mid, int r) {

//        inComparable[] aux = new int[r - l + 1];

//        for (int i = l; i <= r; i++) {

//            aux[i - l] = arr[i];        //临时空间初始化

//        }

        Comparable[] aux = Arrays.copyOfRange(arr, l, r + 1);   //临时空间初始化

        int i = l, j = mid + 1;

        for (int k = l; k <= r; k++) {

            if (i > mid) {

                //如果i遍历结束

                arr[k] = aux[j - l];

                j++;

            } else if (j > r) {

                //如果j遍历结束

                arr[k] = aux[i - l];

                i++;

            } else if (aux[i - l].compareTo(aux[j - l]) < 0) {

                arr[k] = aux[i - l];

                i++;

            } else {

                arr[k] = aux[j - l];

                j++;

            }

        }

    }

}

 

ps:这里的重点是使用临时空间进行比较,在真实空间进行赋值。

 

四、快速排序(Quick Sort)_一路快排 O(nlogN)

是一种递归的排序算法,从未排序的元素中随机找到一个元素(比如4),

然后想办法让这个元素(4)放到它应该在的位置。这样把数组一分为两。

然后按照此方法把大于4和小于4的数组段进行排序。

1、算法描述

Partition就是将数组一分为两的过程。

L是参照元素、j是分界点指向小于V的最后元素、i表示当前访问的元素

比较的过程分两种情况:

1)e>=v

这种情况比较简单,直接i++就可以,不用做其它操作。

2)e<v

这种情况下将j++位置的元素(大于V)与e进行值交换。i++

三步  1、j++  2、swap  3、i++

private static int partition(Comparable[] arr, int l, int r) {

    Comparable v = arr[l];

    int j = l;

    for (int i = l + 1; i <= r; i++) {

        if (arr[i].compareTo(v) < 0) { //e<v的情况

            j++;

            swap(arr, j, i);

        }

    //对于大小和等于的情况不作处理

    }

    swap(arr, l, j);

    return j;

}

 

2、动画演示:

3、代码实现

public class Quick1Sort {

    public static void sort(Comparable[] arr) {

        sort(arr, 0, arr.length - 1);

    }

 

    private static void sort(Comparable[] arr, int l, int r) {

        if (l >= r) {

            return;

        }

        int p = partition(arr, l, r);   //空位分段过程

        sort(arr, l, p - 1);

        sort(arr, p + 1, r);

    }

 

    //定位

    private static int partition(Comparable[] arr, int l, int r) {

        Comparable v = arr[l];

        int j = l;

        for (int i = l + 1; i <= r; i++) {

            if (arr[i].compareTo(v) < 0) {

                j++;

                swap(arr, j, i);

            }

        }

        swap(arr, l, j);

        return j;

    }

 

    private static void swap(Comparable[] arr, int i, int j) {

        Comparable tmp = arr[i];

        arr[i] = arr[j];

        arr[j] = tmp;

    }

 

    public static void main(String[] args) {

        Integer[] arr = {4, 2, 5, 6, 1, 2};

        Quick1Sort.sort(arr);

        System.out.println(Arrays.toString(arr));

    }

}

 

4、局限性

1、对于近乎有序的数组,可能退代为O(n^2)(所以说对于近乎有序的数组,归并排序要优于一路快排)

优化:

    选择参照元素时,不要用第一个元素,可以随机选择一个元素。然后和第一个元素进行交换

    swap(arr, l, (int) Math.random() * (r - l + 1) + l);

2、对于有大量重复键值的情况。可能退代为O(n^2).这时也不如归并排序

我们发现当有大量重复,可能会分为极度不平衡的两段。

优化:这时我们可以使用两路快排。解决重复元素的分布情况。

 

四、快速排序(Quick Sort)_两路快排 O(nlogN)

解决有大量重复元素的数组进行一路快排时的效率问题。它优化的只是patition的方法。目的是分散==V元素的分布情况。

思路就是把小于V和大小V的元素放到两端。

当发现i的值>=V 停止,当j的值<=V也停止。然后将i和j的值进行交换。i++,j--继续进行

 

这里其实也有问题,也就是说 ==V的值被分到了两站。不再向单路快排那样集中而已。还是有问题。

V参照点

i左边比较元素

j右边比较元素

代码实现:

private static int partition(Comparable[] arr, int l, int r) {

    Comparable v = arr[l];

    int i = l + 1, j = r;

    while (true) {

        while (i <= r && arr[i].compareTo(v) < 0) {

            i++;

        }

        while (j >= l + 1 && arr[j].compareTo(v) > 0) {

            j--;

        }

        if (i > j) {

            break;

        }

        swap(arr, i, j);

        i++;

        j--;

    }

    swap(arr, l, j);

    return j;

}

 

待优化点:

1、解决了大量重复元素的分散问题。但对于重复元素我们还是要再次进行排序。

    优化:对于重复元素一但发现和V相等,以后就不再进行排序。接下来引入我们的三路快排

 

四、快速排序(Quick Sort)_三路快排 O(nlogN)

解决的重复元素多次排序的问题。将数组分为三段,<小于、 =等于、 >大于 参数元素值。

 

1、算法描述

V参照点,这个不用多说

i当前元素

LT指向最后一个小于V的位置

GT指向第一个大小V的位置

1)它是一路快速的基础上发展出来的。也就是说一路快排可以直接作为成三路快排就可以,两路快排了解一下就行。

2)通过lt和gt将数组分成三段,也就是有两个标定点lt\gt和一路快排不同,一路快排只有一个p标定点

3)根据上图我们讨论三种情况

  • e>v时

    gt-1 的位置与e进行交换,gt--

    由于gt--的位置元素没有进行比较过,所以i不变

    if (arr[i].compareTo(v) > 0) {

      swap(arr, i, gt - 1);

     gt--;

    }

  • e<v时

    e与 lt+1的元素进行交换然后 lt++。lt+1的元素肯定是>=V的且是比较过的。这没问题吧。

    由于两个交换的元素都是比较过的所以i++继续进行。

    if (arr[i].compareTo(v) < 0) {

    swap(arr, i, lt + 1);

    lt++;

    i++;

}

  • e==v时

    这个最简单也不用改变lt和gt的位置,直接i++

    i++;

    

2、代码如下:

public class Quick3Sort {

    public static void sort(Comparable[] arr) {

        sort(arr, 0, arr.length - 1);

    }

 

    private static void sort(Comparable[] arr, int l, int r) {

        if (l >= r) {

            return;

        }

 

        Map<String, Integer> p = patition(arr, l, r);

 

        sort(arr, l, p.get("lt") - 1); //最后lt会放到=V的第一下值,因为刚才的交换swap(arr, l, lt);

        sort(arr, p.get("gt"), r);

    }

 

    private static Map<String, Integer> patition(Comparable[] arr, int l, int r) {

        int lt = l;

        int gt = r + 1;

        Comparable v = arr[l];

        for (int i = l + 1; i < gt; ) {

            if (arr[i].compareTo(v) < 0) {

                swap(arr, i, lt + 1);

                lt++;

                i++;

            } else if (arr[i].compareTo(v) > 0) {

                swap(arr, i, gt - 1);

                gt--;

            } else {

                i++;

            }

        }

        swap(arr, l, lt);

 

        Map res = new HashMap();

        res.put("lt", lt);

        res.put("gt", gt);

        return res;

    }

 

 

    private static void swap(Comparable[] arr, int i, int j) {

        Comparable tmp = arr[i];

        arr[i] = arr[j];

        arr[j] = tmp;

    }

 

    public static void main(String[] args) {

        Integer[] arr = {4, 2, 5, 6, 1, 2};

        Quick3Sort.sort(arr);

        System.out.println(Arrays.toString(arr));

    }

}

 

3、总结

它是对一路快排的优化,它的思路是将数组分为三段,==V的这一段,以后不再进行排序。

 

 

五、堆排序(O(nlogN))

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

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,则整个排序过程完成。

2 动图演示

3、代码实现

public static void sort_min(List list){

    //java中默认是最小堆

    PriorityQueue heap = new PriorityQueue(list);

    int size = heap.size();

    for (int i = size - 1; i >= 0; i--) {

        list.set(i,heap.remove());

    }

}

 

public static void sort_max(List list){

    PriorityQueue heap = new PriorityQueue(list);

 

    int size = heap.size();

 

    for (int i = size - 1; i >= 0; i--) {

        list.set(i,heap.remove());

    }

    Collections.reverse(list);   //进行反转

}

 

最后对算法进行总结:

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值