排序算法Java版(基础篇)

一、冒泡排序

	/*
    冒泡排序:依次选择第i位置上可选的最小数
    [7,2,5,3,4] for j in [i+1->n] compare nums[i]和nums[j]
    i=0 | [7,2,5,3,4](2,7,5,3,4) => [2,7,5,3,4] => [2,7,5,3,4] => [2,7,5,3,4]
           i ^                       i   ^          i     ^        i       ^
    i=1 | [2,7,5,3,8](2,5,7,3,4) => [2,5,7,3,8](2,3,7,5,4) => [2,3,7,5,4]
             i ^                       i   ^                     i     ^
    i=2 | [2,3,7,5,4](2,3,5,7,4) => [2,3,5,7,4](2,3,4,7,5)
               i ^                       i   ^
    i=3 | [2,3,4,7,5](2,3,4,5,7)
                 i ^
     */
    public static void bubbleSort(int[] nums) {
        int n = nums.length;
        for (int i = 0; i < n - 1; i++) {
            for (int j = i + 1; j < n; j++) {
                if (nums[j] < nums[i]) {
                    swap(nums, i, j);
                }
            }
        }
    }
    
	private static void swap(int[] nums, int i, int j) {
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }

二、插入排序

	/*
    插入排序:将数组划分成两个子区间 L[1..i-1](有序区)和 R[i..n](无序区)
    插入排序与打扑克时整理手上的牌非常类似
    [7,2,5,3,4] for i in [1,n] compare nums[i]和nums[left]
    目的:找到比最后一个nums[i]大的位置!!!该位置就是nums[i]的应有的座次
    i=1 tmp=nums[1]=2 | [7,2,5,3,4]->(7,7,5,3,4) --结束循环--> [2,7,5,3,4] 临时值
                         ^ i
    i=2 tmp=nums[2]=5 | [2,7,5,3,4]->(2,7,7,3,4) => [2,7,7,3,4] --结束循环--> [2,5,7,3,4] 2的下一个元素7就是最后一个比tmp小的位置,后同理
                           ^ i                       ^
    i=3 tmp=nums[3]=3 | [2,5,7,3,4]->(2,5,7,7,4) => [2,5,7,7,4]->(2,5,5,7,4) => [2,5,5,7,4] --结束循环--> [2,3,5,7,4]
                             ^ i                       ^   i                     ^     i
    i=4 tmp=nums[4]=4 | [2,3,5,7,4]->(2,3,5,7,7) => [2,3,5,7,7]->(2,3,5,5,7) => [2,3,5,5,7] --结束循环--> [2,3,4,5,7]
                               ^ i                       ^   i                     ^     i
     */
    public static void insertSort(int[] nums) {
        int n = nums.length;
        int tmp;
        for (int i = 1; i < n; i++) {
            tmp = nums[i];
            int left = i - 1;
            while (left >= 0) {
                // System.out.println(String.format("nums[%s]=%s nums[%s]=%s", i, tmp, left, nums[left]));
                if (tmp < nums[left]) { // 比待插入的数大 继续左移
                    nums[left + 1] = nums[left];
                    left--;
                } else { // left及左面元素都比tmp小,跳出循环
                    break;
                }
            }
            // 此时left=-1 || left为第一个不大于tmp的位置。left+1为最后一个小于tmp的位置
            nums[left + 1] = tmp;
        }
    }

三、快速排序

	public static void quickSort(int[] nums, int left, int right) {
	    if (left < right) {
	        int p = partition(nums, left, right);
	        quickSort(nums, left, p - 1);
	        quickSort(nums, p + 1, right);
	    }
	}
	
	/*
	快速排序:采用算法导论中的划分元方法
	      [7,2,5,3,4] => [7,2,5,3,4]->[2,7,5,3,4] => [2,7,5,3,4] => [2,7,5,3,4]-> [2,3,5,7,4] --循环结束,交换划分元位置--> [2,3,4,7,5] ==>子数组 [2,3] && [7,5]
	x=4  i j              i j                         i   j            i   j
	      [2,3] => [2,   3] --循环结束,交换划分元位置--> [2,3] ==> 子数组 [2]
	x=3  i j        i(j)
	      [7,5] => [5,   7] --循环结束,交换划分元位置--> [5,7] ==> 子数组 [5]
	x=5  i j        i(j)
	 */
	private static int partition(int[] nums, int left, int right) {
	    int i = left - 1;
	    int x = nums[right]; // 划分元,一般选择末尾元素
	    for (int j = left; j < right; j++) { // for j in [left, right - 1]
	        if (nums[j] <= x) { // 保障i以及左面的元素都比划分元小
	            i++;
	            swap(nums, i, j);
	        }
	    }
	    // 最后循环结束,交换划分元的位置,保障划分元左侧元素小于等于自己,右侧元素大于自己
	    swap(nums, i + 1, right);
	    // 返回划分元的位置
	    return i + 1;
	}
	
	private static void swap(int[] nums, int i, int j) {
	    int tmp = nums[i];
	    nums[i] = nums[j];
	    nums[j] = tmp;
	}

四、归并排序

	/*
    归并排序
    1、利用递归,不断得寻找左子数组和右子数组,一直到数组的长度为一
    2、非原址排序,新创建一个临时数组
    ->[] 标识从上一步拆分而来
    []-> 表示下一步归并成新的结果
    [7,2,5,3,4]
         |------>[7,2,5]
         |           |-------->[7,2]
         |           |           |------>[7]->
         |           |           |            |--->[2,7]->
         |           |           |------>[2]->           |
         |           |                                   |---->[2,5,7]->
         |           |-------->[5]----------------------->              |
         |                                                              |--->[2,3,4,5,7]
         |------->[3,4]                                                 |
                     |-------->[3]----------->                          |
                     |                        |--->[3,4]---------------->
                     |-------->[4]----------->
     */
    public static void mergeSort(int[] nums, int left, int right) {
        if (left < right) {
            int mid = (left + right) / 2;
            mergeSort(nums, left, mid);
            mergeSort(nums, mid + 1, right);
            merge(nums, left, mid, right);
        }
    }

    private static void merge(int[] nums, int left, int mid, int right) {

        // 辅助数组
        int[] tmp = new int[nums.length];
        int i = left;

        int p1 = left, p2 = mid + 1;

        while (p1 <= mid && p2 <= right) {
            if (nums[p1] < nums[p2]) {
                tmp[i++] = nums[p1++];
            } else {
                tmp[i++] = nums[p2++];
            }
        }

        // 只有一边数组存在未遍历元素
        while (p1 <= mid) {
            tmp[i++] = nums[p1++];
        }
        while (p2 <= right) {
            tmp[i++] = nums[p2++];
        }

        // 拷贝到原数组
        for (int j = left; j <= right; j++) {
            nums[j] = tmp[j];
        }
    }

五、堆排序

	/*
    堆排序
    [7,2,5,3,4]
               7(0)
            /       \
          2(1)     5(2)
         /     \
       3(3)    4(4)
       leftChild=2*i+1 && rightChild=2*i+2
       parent=(i-1)/2

       以大根堆为例
            思路说明:排序的核心分为
                1、整堆(一次调整以某个节点为根时堆的顺序,比如下面调整其局部结构为:
                     2(current)               4
                    / \   =>                 /  \
                   3   4                    3    2(current)
                2、建堆(初始化建堆,即从第一个非叶子节点(叶子节点本身符合大堆特性)倒序开始 整堆,直到调整完最后一个非叶子节点;此时符合大根堆属性)
                3、交换堆顶元素和堆最后一个元素。将堆分为 [有序] 和 [无序] 两个部分
       1、初始化建堆
       2、取堆顶元素和堆最后一个元素交换。
            初始时 无序=[1,2..n] 有序=[] ===> 无序[1,2...n-1] 有序[n]
       3、步骤2以后,无序堆的大小-1,且新的堆可能不符合大根堆属性(但其左右孩子仍然为大根堆)
            所以直接从位置0重新整堆,即整体调整为大根堆。
       4、重复步骤2和3直到无序堆大小为1(剩余的最小的元素)。此时全部有序

     */
    public static void heapSort(int[] nums) {
        // 初始化的堆大小可以是数组长度,此处我选择的定义为无序堆的最后一个下标
        int heapSize = nums.length - 1;
        buildHeap(nums, heapSize);
        while (heapSize >= 1) {
            swap(nums, 0, heapSize); // 将堆顶元素和堆最后一个元素交换。无序堆大小-1
            // 此时根节点的左右孩子依然保持大(小)根堆的特性
            heapSize--;
            adjustHeap(nums, heapSize, 0);
        }
    }

    // 建堆
    private static void buildHeap(int[] nums, int heapSize) {
        // 最后一个父节点 (叶子节点本身就是个堆)
        int parent = getParent(heapSize);
        // 初始化调整堆 针对每一个非叶子节点倒序
        for (int i = parent; i >= 0; i--) {
            adjustHeap(nums, heapSize, i);
        }
    }

    // 整堆
    private static void adjustHeap(int[] nums, int heapSize, int i) {
        int left = getLeftChild(i);
        int right = getRightChild(i);
        // 记录自身节点和左右孩子最大值的位置
        int largest = i;
        // 取左右孩子的最大值位置
        if (left <= heapSize && nums[left] > nums[largest]) {
            largest = left;
        }
        if (right <= heapSize && nums[right] > nums[largest]) {
            largest = right;
        }
        if (largest != i) { // 自身并非最大值处,自左孩子或者右孩子继续向下整堆
            swap(nums, i, largest);
            adjustHeap(nums, heapSize, largest);
        }
    }

    private static int getLeftChild(int i) {
        return 2 * i + 1;
    }

    private static int getRightChild(int i) {
        return 2 * i + 2;
    }

    private static int getParent(int i) {
        return (i - 1) / 2;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值