必备代码(二):八大排序(链表/数组)

前言

八大排序可以算是《数据结构与算法》这门课的作为经典的一环,不管是期末考试还是面试都有它的存在。面试中,手写还是说思路都屡见不鲜了。面试中还有一种问法是“快排的第二次”、“归并的第三次”,这就要你不但要求你会写,而且脑中要有一个排序的过程。另外分析时间空间复杂度也是一定会被考察的,难一点还会问最优最差的复杂度,因此思考算法的同时,还要想一些例子。本文会将分析排序的大致思想、复杂度,部分算法将同时给出链表的实现。废话不多说,直接开始吧。
力扣排序数组

力扣排序链表

冒泡排序

冒泡排序的“冒泡”可以看作一个数值移动的过程。每回合都从头开始比较,通过两两交换,最终将一个当前回合的极值固定到尾部,下一回合只需要对剩下的数做上述过程即可。

数组的冒泡排序

    public int[] sortArray_bubble(int[] nums) {
   
        for (int i = 0; i < nums.length-1; i++) {
   //当前轮有i个数被固定
            boolean b = false;
            for (int j = 0; j+1 < nums.length-i; j++) {
   //末尾有i个数被固定
                if(nums[j+1]<nums[j]){
   
                    swap(j,j+1,nums);
                    b=true;
                }
            }
            if(!b)break;
        }
        return nums;
    }

做两点说明:数组交换swap和链表交换swapListNode都是简单的值交换,不再给出具体实现。同时方法内部已经做了优化(不会交换相同的值)

代码做了优化:如果一次冒泡没有进行任何交换,那么便认为整个数组有序,退出。
几个注意点:
【1】外循环控制的“当前回合有几个值被固定了/上浮完毕了”,我们在内循环减去这个值,没有比较的必要了。其中减一的意思是“最后一轮只有一个元素未固定,那么没有比较的必要了,直接退出即可”
【2】内循环如果写作 j < len - i -1 我认为可读性有点差,当你初学时死活不懂课本上写的什么意思。其实就是一个越界检查罢了。初学者容易写成 j = i ,就是没有理解内循环的意义罢了。内循环就是一个从头开始,和相邻元素两两比较的过程,唯一使用到外循环变量的就是知道“哪些没必要比较”
【3】没有使用额外空间,因此空间复杂度O(1),最好的情况一次内循环发现没有任何交换直接退出,因此O(N),最坏情况从头比到尾O(N^2)

链表的冒泡排序

冒泡排序说白了:外层控制终点,内层从起点开始两两比较,用链表实现一个道理,使用一个指针标记结尾,一个指针标记开头。外循环修改尾指针,内循环每回合都重置头指针。

    public ListNode bubbleSort(ListNode head){
   
        if(head == null || head.next == null)
            return head;
        ListNode tail = null;
        ListNode cur = head;
        while(cur.next != tail){
   
            boolean flag=false;//没有交换
            while(cur.next != tail){
   
                if(cur.val > cur.next.val){
   
                    swapListNode(cur,cur.next);
                    flag=true;
                }
                cur = cur.next;
            }
            if(!flag)break;
            tail = cur;  //“气泡固定”通过尾指针移动减少范围来表现
            cur = head;     //从头再来
        }
        return head;
    }

选择排序

选择排序选择什么?常规的升序降序选择的是机制,当然算法就是一个思想,不同的应用选择的值也不一样。选择排序的思想:每次选择一个极值,然后存入目标集合,直到选择完毕所有元素。

数组的选择排序

以升序为例:

    private void selectSort(int[] arr)
    {
   
        //最终一轮剩下的就是非最小值,因此 arr.length-1
        for (int i = 0; i < arr.length-1; i++) {
   
            int minIndex = selectMin(arr, i, arr.length);
            if(minIndex!=i){
   //当前索引对应的不是最小值
                swap(i,minIndex,arr);//交换
            }
        }
    }

选择的范围是不断缩小的,依次选出最小值、次最小值、直到选择完毕所有的值

    private int selectMin(int[] arr, int start, int end) {
   
        int min=Integer.MAX_VALUE;
        int minIndex=0;
        for (int i = start; i < end; i++) {
   
            if(arr[i]<min){
   
                min=arr[i];
                minIndex=i;
            }
        }
        return minIndex;
    }

选择排序的复杂度总是O(n^2),因为即使给出的数组是有序的,选择时也是感觉不到的,只知道“当前我要选择的是当前范围的最值”。
选择排序算法是不稳定的,如552,第一次选择完毕变成255,其中两个5的相对位置被改变了。

链表的选择排序

如果把链表看出有序与无序两个部分,一个指针指向无序链表的头结点,一个指针用于扫描无序链表做选择,扫描的过程中两个指针指向的节点的值将做交换。扫描完毕后,无序头结点便是当前无序链表中最小的,加入有序链表尾部,更新无序链表头部…指定全部节点被加入有序链表

其实也可以使用一个指针保存最小节点,扫描完毕后进行一次节点的交换,避免了过程中重复的值交换。总之实现方法很多。

    public ListNode sortList(ListNode head) {
   
        if(head == null){
   
            return head;
        }
        ListNode curNode = head;
        while(curNode!=null){
   
            ListNode nextNode = curNode.next;
            while(nextNode!=null){
   
                //和更小的节点交换值
                if(curNode.val > nextNode.val){
   
                    swapListNode(curNode,nextNode);
                }
                nextNode = nextNode.next;
            }
            curNode = curNode.next;
        }
        return head;
    }

插入排序

之前提到了,可以将序列看作两部分:有序的和无序的。选择排序每次从无序序列中选择一个极值。而插入排序不对无序序列进行选择(扫描),而是对有序序列进行选择(插入)。插入排序每次取无序序列首元素,然后再从有序序列中选择一个合适的位置,进行插入即可。
由于扫描的是有序序列,因此可以“感知”扫描停止的时机,不像扫描无序序列那样,必须全部扫描,生怕漏了那个数。

数组的插入排序

插入排序可以分为直接插入排序和二分插入排序,后者更快,对有序序列进行二分插入,而不是从一端向令一端遍历。(但是仍然无法避免数组数据的整体移位)
直接插入排序:

    public void insertSort(int[] arr){
   
        for (int i = 0; i+1 < arr.length; i++) {
   
            //定义待插入的数
            int insertVal = arr[i+1];
            //给insertVal 找到插入的位置
            int indexPreIndex;
            for( indexPreIndex= i;indexPreIndex>=0 && insertVal<arr[indexPreIndex];indexPreIndex--){
   
                arr[indexPreIndex+1]=arr[indexPreIndex];//整体后移
            }
            arr[indexPreIndex+1]=insertVal;//insertVal>=arr[indexPreIndex],要插入到后面的位置保证有序
        }
    }

二分插入排序:

    public void insertSort2(int
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值