算法学习之路(九)—— 排序算法

分治思想

1.分解
2.解决子问题
3.合并

题目: 数组排序

一:插入排序

1. 直接插入排序

	public void insertSort(int[] arr, int len) {
        int i, j;
        for (i = 2; i < len; i++) {
            if (arr[i] < arr[i - 1]) {
                arr[0] = arr[i];//arr[0]为哨兵,不作排序的元素
                for (j = i - 1; arr[j] > arr[0]; j--) {
                    arr[j + 1] = arr[j];
                }
                arr[j + 1] = arr[0];
            }
        }
    }

2. 折半插入排序

	public void insertSort02(int[] arr, int len) {
        int i, j, low, high, mid = 0;
        for (i = 2; i < len; i++) {
            if (arr[i] < arr[i - 1]) {
                low = 1;
                high = i;
                arr[0] = arr[i];//arr[0]不作为排序元素
                while (low <= high) {//二分搜索,注意临界条件
                    mid = low + ((high - low) >> 1);
                    if (arr[mid] < arr[0]) low = mid + 1;
                    else high = mid - 1;
                }
                for (j = i - 1; j >= high + 1; j--)
                    arr[j + 1] = arr[j];
                arr[high + 1] = arr[0];
            }
        }
    }

3. 希尔排序

在这里插入图片描述

	public void shellSort(int[] arr, int len) {
        int i, j, tmp;
        for (int dk = len / 2; dk >= 1; dk /= 2) //步长,最小为1
            for (i = dk; i < len; i++)          // 接下来就是直接插入排序
                if (arr[i] < arr[i - dk]) {
                    tmp = arr[i];                //暂存于tmp中
                    for (j = i - dk; j >= 0 && arr[j] > tmp; j -= dk)
                        arr[j + dk] = arr[j];
                    arr[j+dk] = tmp;
                }
    }

交换排序

1. 冒泡排序

	 public void bubbleSort(int[] arr, int len) {
        int i, j;
        boolean flag = false;
        for (i = len - 1; i >= 0; i--) {
            for (j = 0; j < i; j++) {
                if (arr[j] > arr[j + 1]) {
                    swap(arr, j, j + 1);
                    flag = true;
                }
            }
            if(!flag)
                break;
        }
    }

2. 快速排序

双向扫描分区法

注意临界条件的判断

import java.util.Arrays;

class Solution {
    public int[] sortArray(int[] nums) {
        quickSort(nums, 0, nums.length - 1);
        return nums;
    }

    private void quickSort(int[] nums, int start, int end) {
        if (start < end) {
            int pos = partition(nums, start, end);
            quickSort(nums, start, pos - 1);
            quickSort(nums, pos + 1, end);
        }
    }

    private int partition(int[] nums, int start, int end) {
        int base = start;
        int left = base + 1;
        int right = end;
        while(left <= right){//在我的算法中,临界条件全部是left<=right,一定要注意。
            while (left <= right && nums[left] <= nums[base]){
                left++;
            }
            while (left <= right && nums[right] >= nums[base]){
                right--;
            }
            if(left < right)
                swap(nums,left,right);
        }
        swap(nums,base,right);
        return right;
    }

    private void swap(int[] nums, int base, int right) {
        int t = nums[base];
        nums[base] = nums[right];
        nums[right] = t;
    }
}

可优化的地方: 优化的地方在于base的取值

选择排序

1. 简单选择排序

思想:i趟在下标为i的后面数组中的数字找找到最小的数字,并且与arr[i]比较。如果小就交换。
这样每次就确定了第i个数的位置,以后都不改变

 	public void selectSort(int[] arr, int n) {
        int min;
        for (int i = 0; i < n; i++) {
            min = i;
            for (int j = i + 1; j < n; j++) {
                if (arr[j] < arr[min])
                    min = j;
            }
            if (min != i) swap(arr, min, i);
        }
    }

2. 堆排序

  1. 什么是堆
    概念: 二叉堆是一个完全二叉树
    特性:
    1). 父节点的键值总是大于等于(或者小于等于)任何一个子节点的键值
    2). 每个节点的左右子树都是一个二叉堆(要么是大顶堆要么是小顶堆)

  2. 思想:
    2.1 把数组大/小顶堆化(因为叶子结点不具有孩子节点,所以已经满足堆的要求了。所以要从A.lenth/2 - 1的位置开始调用辅助构造方法来保证该节点作为根节点时,对于整个堆来说,这个局部时大顶堆或者小顶堆)
    2.2 设计辅助构造大小顶堆的方法
    传入数组,局部根节点的索引,和数组最后节点的索引。来保证局部是大/小顶堆
    2.3. 让根节点每次都和最后一个元素交换,但是比较完一次后,数组的最后一个元素要减 1 。然后调用辅助构造大/小顶堆的方法,重新构造出一个长度减1的大/小顶堆。这样数组从最后一个位置开始依次往前被填充有序数据。直到整个数组有序

import java.util.Arrays;

class Solution {
    public int[] sortArray(int[] nums) {
        heapSort(nums);
        return nums;
    }

    private void heapSort(int[] nums) {
        int len = nums.length;
        for (int i = len / 2 - 1; i >= 0; i--)
            maxHeapize(nums, i, len - 1);
        while (len > 0) {
            len -= 1;
            swap(nums, 0, len);
            maxHeapize(nums, 0, len - 1);
        }
    }

    private void maxHeapize(int[] nums, int root, int len) {
        int lch = root * 2 + 1;
        int rch = root * 2 + 2;
        if (rch > len) {//说明无右孩子
            if (lch > len) {//说明也无左孩子,那么就是叶子节点
                return;
            }
            //存在左孩子,而无右孩子
            if (nums[lch] < nums[root])
                return;
            swap(nums, lch, root);
            maxHeapize(nums, lch, len);//左孩子继续向下调整
        } else {
            //说明有左右孩子,注意不存在只有右孩子,没有左孩子的情况。
            int max = Math.max(nums[lch], nums[rch]);
            if (nums[root] >= max) return;
            if (nums[lch] > nums[rch]) {
                swap(nums, lch, root);
                maxHeapize(nums, lch, len);
            } else {
                swap(nums, rch, root);
                maxHeapize(nums, rch, len);
            }
        }
    }
    private void swap(int[] array, int a, int b) {
        int tmp = array[a];
        array[a] = array[b];
        array[b] = tmp;
    }
}

归并排序和基数排序

1. 归并排序

  1. 和快速排序的区别

    1.不用把大的和小的分区
    2.需要新的辅助空间(拷贝原数组)
    3.归并排序的有序性是靠分别比较两个区的头位置

merge的参数
void merge(arr,start,mid,end)

import java.util.Arrays;

class Solution {
    public int[] sortArray(int[] nums) {
        mergeSort(nums, 0, nums.length - 1);
        return nums;
    }

    private void mergeSort(int[] nums, int start, int end) {
        if (start < end) {
            int mid = start + ((end - start) >> 1);
            mergeSort(nums, start, mid);
            mergeSort(nums, mid + 1, end);
            merge(nums, start, mid, end);
        }
    }

    public void merge(int[] arr, int L, int mid, int R) {
        int[] temp = new int[R - L + 1];
        int i = 0;
        int p1 = L;
        int p2 = mid + 1;
        // 比较左右两部分的元素,哪个小,把那个元素填入temp中
        while (p1 <= mid && p2 <= R) {
            temp[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
        }
        // 上面的循环退出后,把剩余的元素依次填入到temp中
        // 以下两个while只有一个会执行
        while (p1 <= mid) {
            temp[i++] = arr[p1++];
        }
        while (p2 <= R) {
            temp[i++] = arr[p2++];
        }
        // 把最终的排序的结果复制给原数组
        for (i = 0; i < temp.length; i++) {
            arr[L + i] = temp[i];
        }
    }

    public static void main(String[] args) {
        int[] nums = {1, 4, 5, 2, 6, 9, 8, 7};
        System.out.println(Arrays.toString(new Solution().sortArray(nums)));
    }
}

2. 基数排序

思想
第一次按个位数将各个数字分配到0~9号桶当中然后出桶
第二次按十位数将各个数字分配到0~9号桶当中然后出桶。。。。。依次类推

通常用于十进制数字

class Solution {
    public int[] radixSort(int[] nums) {
        int[] res = fixAndGetMaxLen(nums);//基数排序解决存在负数的情况,先调整数组
        int min = res[0];
        int size = res[1];
        int[][] buckets = new int[10][nums.length];
        int[] p = new int[10];//记录每个桶的最后一个元素的位置
        int n = 1;
        for (int i = 0; i < size; i++) {//进行n次按关键字排序, 采取的方式:LSD(最低为优先)
            for (int j = 0; j < nums.length; j++) {
                int m = (nums[j] / n) % 10;
                buckets[m][p[m]] = nums[j];
                p[m]++;//指向桶中存下一个数的位置
            }
            int index = 0;
            for (int j = 0; j < p.length; j++)
                if (p[j] != 0) {
                    for (int k = 0; k < p[j]; k++)
                        nums[index++] = buckets[j][k];
                    p[j] = 0;//相当于清空该桶中元素
                }
            n *= 10;
        }
        if(min < 0)
            for (int i = 0; i < nums.length; i++)
                nums[i] += min;
        return nums;
    }

    private int[] fixAndGetMaxLen(int[] nums) {
        int max = Integer.MIN_VALUE, min = Integer.MAX_VALUE;
        for (int n : nums) {
            max = Math.max(max, n);
            min = Math.min(min, n);
        }
        if (min < 0) {
            for (int i = 0; i < nums.length; i++)
                nums[i] -= min;//每个数减去最小的那个负数。使得数组中每个数都 >=0
            max -= min; //相应的数组中的最大值也要减去min,才是现在数组的最小值。
        }
        return new int[]{min, (max + "").length()};//返回max是几位数 与 min
    }
}

桶排序和计数排序

1. 计数排序

思想

把数组的值映射到一个辅助空间的索引上,每次 +1;

缺点

如果数据特别的稀疏,那么会浪费大量的空间
class Solution {
    public int[] sortArray(int[] nums) {
        countSort(nums);
        return nums;
    }

    private void countSort(int[] nums) {
        int[] store = new int[100001];//这里开辟100001的原因是题目中说明了数据大小的范围是[-50000,50000];
        for (int i = 0; i < nums.length; i++)
            store[nums[i] + 50000]++;
        int current = 0;
        for (int i = 0; i < store.length; i++) {
            if (store[i] != 0) {
                while (store[i]!=0) {
                    nums[current++] = i - 50000;
                    store[i]--;
                }
            }
        }
    }
    
}

2. 桶排序

思想

将数组中的数按照一定的运算规则(不固定)分配到一些桶当中。保证桶中的数据是有序的,然后依次出桶(从桶中把数据取出来)
说明:
桶排序更是对计数排序的改进,计数排序申请的额外空间跨度从最小元素值到最大元素值,若待排序集合中元素不是依次递增的,则必然有空间浪费情况。桶排序则是弱化了这种浪费情况,将最小值到最大值之间的每一个位置申请空间,更新为最小值到最大值之间每一个固定区域申请空间,尽量减少了元素值大小不连续情况下的空间浪费情况。

import java.util.Arrays;

class Solution {
    public int[] sortArray(int[] nums) {
        bucketSort(nums);
        return nums;
    }

    public void bucketSort(int[] nums) {
        //1.找最大值,同于计算元素在几号桶
        int len = nums.length;
        Node[] buckets = new Node[len];
        int max = Integer.MIN_VALUE;
        int min = Integer.MAX_VALUE;
        for (int i = 0; i < nums.length; i++){
            max = Math.max(max,nums[i]);
            min = Math.min(min,nums[i]);
        }
        if(min < 0){//处理负数
            for (int i = 0; i < nums.length; i++)
                nums[i] -= min;
            max -= min;
        }
        //2.入桶
        for (int i = 0; i < nums.length; i++) {
            //得到每个元素应该在几号桶里  e*len/(max+1)
            int index = nums[i] * len / (max + 1);
            if (buckets[index] == null) {//如果该桶中还没有元素
                buckets[index] = new Node(nums[i]);
            } else {//该桶有多个元素,应该按序插入
                insertTo(buckets[index], nums[i]);//把头指针和要插入的元素传入参数
            }

        }
        int index = 0;
        //3.出桶
        for (int i = 0; i < buckets.length; i++) {
            if (buckets[i] != null) {
                Node p = buckets[i];
                while (p != null) {
                    if(min < 0)
                        nums[index++] = p.data + min;
                    else
                        nums[index++] = p.data;
                    p = p.next;
                }
            }
        }

    }

    private void insertTo(Node head, int e) {
        Node p = head.next;
        Node pre = head;
        Node newNode = new Node(e);
        while (p != null) {
            if (p.data > e) {//找到第一个大于e的元素的位置
                pre.next = newNode;
                newNode.next = p;
                return;
            }
            pre = p;
            p = p.next;
        }
        //如果一直到最后一个元素也没有大于当前元素
        if (pre.data > e) {
            int tmp = newNode.data;
            newNode.data = pre.data;
            pre.data = tmp;
            pre.next = newNode;
            newNode.next = p;
        } else {
            pre.next = newNode;
        }
    }

}

class Node {
    int data;
    Node next;

    public Node(int data) {
        this.data = data;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值