排序算法复杂度比较

 

快速排序

 基准元素的选取会影响复杂度,最坏的情况可能到 O(n2

  • 选取区间起始元素
  • 选取区间结束元素
  • 区间内随机选取一元素

!!!! 注意下面 这个,一定要先找右边,再找左边

      // 在右边 找到第一个小于 pivot 的(所以大于【等于】的都忽略)
            while (nums[right] >= pivot && left < right) {
                right--;
            }
            // 在左边找到第一个大于 pivot 的(所以小于【等于】的都忽略)
            while (nums[left] <= pivot && left < right) {
                left++;
            }
public class Sort_QuickSort {

    public static void main(String[] args) {
        int[] nums = new int[]{6,9,1,4,8,2,5,7,6};
        Sort_QuickSort quickSort = new Sort_QuickSort();
        quickSort.quickSort(nums, 0, nums.length-1);
        for (int i=0;i< nums.length;i++) {
            System.out.print(nums[i] + " ");
        }
    }

    public void quickSort(int[] nums, int startIndex, int endIndex) {
        if (startIndex >= endIndex) {
            return;
        }
        int pivotIndex = partition(nums, startIndex, endIndex);
        // pivot 左边的元素都比它小,右边的元素都比它大
        // 递归对 pivot 左半做相同的操作
        quickSort(nums, startIndex, pivotIndex-1);
        // 递归对 pivot 右半做相同的操作
        quickSort(nums, pivotIndex+1, endIndex);
    }


    private int partition(int[] nums, int startIndex, int endIndex) {
        // 最后要达到的效果是,pivot 左边的元素都比它小,右边的元素都比它大
        // 选取数组的第一个元素作为基准元素 pivot
        int pivotIndex = startIndex;
        int pivot = nums[pivotIndex];
        // 左右指针
        int left = startIndex;
        int right = endIndex;
        while (true) {
            // 在右边 找到第一个小于 pivot 的(所以大于【等于】的都忽略)
            while (nums[right] >= pivot && left < right) {
                right--;
            }
            // 在左边找到第一个大于 pivot 的(所以小于【等于】的都忽略)
            while (nums[left] <= pivot && left < right) {
                left++;
            }
            if (left < right) {
                // 交换左右两边
                swap(nums, left, right);
            }
            else {
                // 退出循环
                break;
            }
        }
        // 把最后的位置和 pivot 的位置交换
        swap(nums, left, pivotIndex);
        // 返回最后的位置(现在放的是pivot)
        return left;
    }

    private void swap(int[] nums, int a, int b) {
        int tmp = nums[a];
        nums[a] = nums[b];
        nums[b] = tmp;
    }
}

 

 

 

堆排序

前情:

  • 完全二叉树是指从开头到结尾每个节点都有左右孩子,除了最后一个节点可以只有左孩子
  • 完全二叉树可以用数组来表示
  • 用数组表示的完全二叉树,某节点下标 i,那么它的左孩子下标是 2*i+1,右孩子下标是 2*i+2,父节点下标是 i/2-1 

先把数组视为一个普通的二叉树,再把这个普通二叉树调整成大顶堆(从小到大排序就是大顶堆)

调整方法是 从 N/2-1 开始到堆顶0,依次调整:

每次调整一个节点,比较它和它的左右孩子,如果满足大根堆定义直接返回,如果不满足大根堆定义,把孩子中大的那个提上来。然后进行递归,继续调整这个节点。

然后是堆的取顶部最大

for(int i=N-1;i>0;i--)

// 交换最大的堆顶元素到堆尾,堆尾元素交换到了到了堆顶

swap(nums, 0, i)

// 堆的大小每次都减少1,即为 i,调整被换到堆顶的这个元素nums[0]

heapify(nums, i, 0)

 

public class Sort_HeapSort2 {

    public static void main(String[] args) {
        int[] nums = new int[]{6,9,1,4,8,2,5,7,6};
        Sort_HeapSort2 heapSort = new Sort_HeapSort2();
        heapSort.sort(nums);
        for (int i=0;i< nums.length;i++) {
            System.out.print(nums[i] + " ");
        }
    }

    public void sort(int nums[])
    {
        int N = nums.length;
        // 数组中所有视作普通的完全二叉树,经过这个循环调整为大根堆(从小到大排序需要大根堆)
        // 从一半开始,这样包括孩子在内,可以把树中所有孩子都覆盖掉
        // 注意是 i>=0 ,因为堆顶也可能不满足堆定义,要进行调整
        for (int i = N / 2 - 1; i >= 0; i--) {
            heapify(nums, N, i);
        }

     
        // 依次把最大的元素从堆顶移到末尾
        for (int i = N - 1; i > 0; i--) {
            // 先把大顶堆堆顶的最大元素和末尾的元素交换
            swap(nums, 0, i);
            // 堆的大小每次都会减一,即为i,每次调整被推到堆顶的 nums[0]元素。使之满足堆定义
            heapify(nums, i, 0);
        }
    }

    // To heapify a subtree rooted with node i which is
    // an index in arr[]. n is size of heap
    void heapify(int arr[], int N, int i)
    {
        int largest = i; // Initialize largest as root
        int l = 2 * i + 1; // left = 2*i + 1
        int r = 2 * i + 2; // right = 2*i + 2

        // If left child is larger than root
        if (l < N && arr[l] > arr[largest])
            largest = l;

        // If right child is larger than largest so far
        if (r < N && arr[r] > arr[largest])
            largest = r;

        // If largest is not root
        // 当前的不是在它左右孩子中最大的
        if (largest != i) {
            // 交换当前的和孩子中比它大的
            swap(arr, i, largest);
            // 递归,继续调整这个当前的元素(现在被换到了largest的位置)下沉到合适的位置
            heapify(arr, N, largest);
        }
    }

    private void swap(int[] nums, int a, int b) {
        int tmp = nums[a];
        nums[a] = nums[b];
        nums[b] = tmp;
    }
}

还有一种就是,添加元素到堆的时候,并不把全部的元素视作一个普通二叉树,然后把这个普通二叉树从 N/2-1~0 调整成堆

而是从一个元素开始,逐个把元素添加到堆的末尾,再调整这个元素使之上浮到合适的位置;再加入堆末尾,再调整...........整个过程中都是完整的堆

形成大顶堆后,从堆中取出元素的操作和前面一样

public class Sort_HeapSort {

    public static void main(String[] args) {
        int[] nums = new int[]{6,9,1,4,8,2,5,7,6};
        Sort_HeapSort heapSort = new Sort_HeapSort();
        heapSort.sort(nums);
        for (int i=0;i< nums.length;i++) {
            System.out.print(nums[i] + " ");
        }
    }


    public void sort(int[] nums) {
        // 从小到大排序需要构建大根堆,然后每次把大根堆堆顶即 nums[0] 位置最大的元素放到末尾去
        for (int i=1;i<nums.length-1;i++) {
            // 构造大根堆:0~i是大根堆, i~末尾是未调整的元素。所以i要递增
            add2Heap(nums, i);
        }
        // 然后每次把大根堆 0 位置最大的元素放到末尾去
        for (int i=nums.length-1;i>0;i--) {
            // 依次取出堆顶元素:0~i是大根堆, i~末尾是之前取出来的堆顶最大元素。所以i要递减
            getTopFromHeap(nums, i);
        }
    }

    private void swap(int[] nums, int a, int b) {
        int tmp = nums[a];
        nums[a] = nums[b];
        nums[b] = tmp;
    }


    public void add2Heap(int[] nums, int endIndex) {
        // 0~endIndex-1 都是已经堆化好的。现在将 endIndex 位置的元素加入
        int thisElemIndex = endIndex;
        // 完全二叉树,位置为 i 的元素的父节点的位置是 (i-1)/2
        int parentIndex = (thisElemIndex - 1)/2;
        // 如果 this 比它的 parent 大,就交换他们两个,直到 this 到合适的位置(比它的parent小就停下来)
        while (nums[parentIndex] < nums[thisElemIndex] && parentIndex>=0) {
            swap(nums, parentIndex, thisElemIndex);
            thisElemIndex = parentIndex;
            parentIndex = (thisElemIndex - 1)/2;
        }
    }

    public void getTopFromHeap(int[] nums, int endIndex) {
        // endIndex ~ 末尾 都是已经取出来的大堆顶。现在把堆末尾 endIndex 的那个元素放到堆顶 0, 然后调整成大根堆
        swap(nums, 0, endIndex);
        int thisIndex = 0;
        // parent 要比它两个孩子都要小。因此校验这个,如果不满足,找到它需要交换的孩子的坐标
        // 堆顶最大元素 nums[0] 被放到了 nums[endIndex],因此大顶堆边界缩小到了 endIndex-1
        int swapChildIndex = getSwapChildIndex(nums, endIndex-1, thisIndex);
        while (swapChildIndex != -1) {
            // 与需要交换的孩子进行交换
            swap(nums, thisIndex, swapChildIndex);
            // 继续调整,直到比两个孩子都小
            thisIndex = swapChildIndex;
            swapChildIndex = getSwapChildIndex(nums, endIndex-1, thisIndex);
        }
    }

    public int getSwapChildIndex(int[] nums, int endIndex, int parentIndex) {
        int leftChildIndex = 2*parentIndex + 1;
        int rightChildIndex = 2*parentIndex + 2;
        // 没有左孩子(更没有右孩子)
        if (leftChildIndex > endIndex) {
            return -1;
        }
        // 只有左孩子
        else if (rightChildIndex > endIndex) {
            // 不满足比左孩子大的话, 就要交换
            return nums[parentIndex] >= nums[leftChildIndex]? -1:leftChildIndex;
        }
        // 因为是完全二叉树,所以没有【没有左孩子而只有右孩子】的情况
        // 同时有左孩子和右孩子
        else {
            // 比两个孩子都大,不用交换
            if (nums[parentIndex] >= nums[leftChildIndex] && nums[parentIndex] >= nums[rightChildIndex]) {
                return -1;
            }
            // 否则和两个孩子中较大的那个交换
            else {
                return nums[leftChildIndex] > nums[rightChildIndex] ? leftChildIndex : rightChildIndex;
            }
        }
    }
}

 

 

归并排序

 

public class Sort_MergeSort {

    // Driver code
    public static void main(String args[])
    {
        int arr[] = { 8,4,1,3,9,2,5 };
        Sort_MergeSort ob = new Sort_MergeSort();
        // !!! 注意方法里是要包含 r 的,所以这里是 nums.length-1 而不是 nums.length
        ob.sort(arr, 0, arr.length - 1);
    }

    // Main function that sorts arr[l..r] using
    // merge()
    void sort(int arr[], int l, int r)
    {
        if (l >= r) {
            return;
        }

         // Find the middle point
        int m = l + (r - l) / 2;

        // Sort first and second halves
        // 递归左右两半。左半包括m,右半不包括
        sort(arr, l, m);
        sort(arr, m + 1, r);

        // Merge the sorted halves
        // 合并两个有序子数组。这个一定要在递归划分的后面
        merge(arr, l, m, r);
    }

    // Merges two subarrays of arr[].
    // First subarray is arr[l..m]
    // Second subarray is arr[m+1..r]
    void merge(int arr[], int l, int m, int r)
    {
        // Find sizes of two subarrays to be merged
        // 左半数组的长度
        int n1 = m - l + 1;
        // 右半数组的长度
        int n2 = r - m;

        // Create temp arrays
        // 初始化临时数组 L R
        int L[] = new int[n1];
        int R[] = new int[n2];

        // Copy data to temp arrays
        // 把左右两半的数组拷贝到临时数组 L R
        for (int i = 0; i < n1; ++i) {
            L[i] = arr[l + i];
        }
        for (int j = 0; j < n2; ++j) {
            // 注意 arr[m+1+j]
            R[j] = arr[m + 1 + j];
        }

      
        // Merge the temp arrays

        // Initial indices of first and second subarrays
        int i = 0, j = 0;

        // Initial index of merged subarray array
        int k = l;
        /*
        * arr[l:r] = [1,3,4,8,2,5,9]
        * 左右都是有序的了
        * L[0:n1-1] = arr[l:m] = [1,3,4,8]
        * R[0:n2-1] = arr[m:r] = [2,5,9]
        * if(1<2) arr[l:r] = ["1",3,4,8,2,5,9]
        * if(3<2) else arr[l:r] = ["1,2",4,8,2,5,9]
        * if(3<5) arr[l:r] = ["1,2,3",8,2,5,9]
        * if(4<5) arr[l:r] = ["1,2,3,4",2,5,9]
        * if(8<5) else arr[l:r] = ["1,2,3,4,5",5,9]
        * if(8<9) else arr[l:r] = ["1,2,3,4,5,8",9]
         */
        while (i < n1 && j < n2) {
            if (L[i] <= R[j]) {
                arr[k] = L[i];
                // i++ 要放在 if (L[i] <= R[j]) 内层
                i++;
            }
            else {
                arr[k] = R[j];
                j++;
            }
            k++;
        }/*
         * arr[l:r] = [1,3,4,8,2,5,9]
         * 左右都是有序的了
         * L[0:n1-1] = arr[l:m] = ["1,3,4,8"]
         * R[0:n2-1] = arr[m:r] = ["2,5",9]
         * arr[l:r] = ["1,2,3,4,5,8",9]
         * L都走完了,因此不走这里
         */
        // Copy remaining elements of L[] if any
        while (i < n1) {
            arr[k] = L[i];
            i++;
            k++;
        }/*
         * arr[l:r] = [1,3,4,8,2,5,9]
         * 左右都是有序的了
         * L[0:n1-1] = arr[l:m] = ["1,3,4,8"]
         * R[0:n2-1] = arr[m:r] = ["2,5",9]
         * arr[l:r] = ["1,2,3,4,5,8",9]
         * 把R中剩下的9复制进去 arr[l:r] = ["1,2,3,4,5,8,9“]
         */
        // Copy remaining elements of R[] if any
        while (j < n2) {
            arr[k] = R[j];
            j++;
            k++;
        }
    }


    // A utility function to print array of size n
    static void printArray(int arr[])
    {
        int n = arr.length;
        for (int i = 0; i < n; ++i)
            System.out.print(arr[i] + " ");
        System.out.println();
    }

    /* This code is contributed by Rajat Mishra */

}