几种排序算法

https://blog.csdn.net/nyist_zxp/article/details/80401979#commentBox
在这里插入图片描述

1、冒泡排序

  • 思路:假如数据的长度为n,那么需要循环n-1次进行冒泡,每一次冒泡找到最大值,放在数组结尾。
  • 时间复杂度:时间复杂度是O(n^2),最好情况是O(n)。
  • 空间复杂度:O(1),因为不需要额外的存储空间。
  • 最坏情况:数组是逆序的。
  • 稳定性:稳定,因为它实现相邻两个元素之间的交换,没有改变元素的相对位置,所以满足稳定性。

代码:

public static void bubbleSort(int[] array) {
        for(int i=0;i<array.length-1;i++){
            for(int j=0;j<array.length-i-1;j++){
                if(array[j+1]<array[j]){
                    int m=array[j+1];
                    array[j+1]=array[j];
                    array[j]=m;
                }
            }
            printProcess(array,array.length,i);
        }
    }

改进的代码:
由于冒泡的过程中,可能已经是排好序的顺序,那么就不需要执行剩下的冒泡了。

public static void bubbleSort2(int[] array){
        for (int i=0;i<array.length-1;i++){
            boolean flag=false;
            for (int j=0;j<array.length-1-i;j++){
                if(array[j+1]<array[j]){
                    int temp=array[j+1];
                    array[j+1]=array[j];
                    array[j]=temp;
                    flag=true;
                }
            }
            if(!flag){
                break;
            }
        }
    }

2、选择排序

  • 思路:在待排序数据中,选出最小的一个数与第一个位置的数交换;然后在剩下的数中选出最小的数与第二个数交换;依次类推,直至循环到只剩下两个数进行比较为止。
  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)
  • 最坏情况:排序的数和复杂度没关系,属于佛系
  • 稳定性:不稳定,例如有5 8 5 2 9 ,5个元素按从小到大排序,当第一趟排序后形成2 8 5 5 9 ,两个5的相对位置最终排好序后发生了变化。

代码:

private static void sort(int[] arr) {
        if (arr != null && arr.length > 1) {
            for (int i = 0; i < arr.length - 1; i++) {
                int minIndex = i;
                for (int j = i + 1; j < arr.length; j++) {
                    if (arr[minIndex] > arr[j]) {
                        minIndex = j;
                    }
                }
                if (minIndex != i) {
                    int min = arr[minIndex];
                    arr[minIndex] = arr[i];
                    arr[i] = min;
                }
                System.out.println("简单选择排序");
                print(arr, i);
            }
        }
    }

改进的代码:
简单选择排序算法,每次只选择一个数据放在正确的位置,为提高效率,可每次选择两个数据,放在正确的位置,及每次选择最大值和最小值

private static void sort2(int[] arr) {
        if (arr != null && arr.length > 1) {
            for (int i = 0; i < arr.length / 2; i++) {
                int minIndex = i;
                for (int j = i + 1; j < arr.length-i; j++) {
                    if (arr[minIndex] > arr[j]) {
                        minIndex = j;
                    }
                }
                if (minIndex != i) {
                    int min = arr[minIndex];
                    arr[minIndex] = arr[i];
                    arr[i] = min;
                }
                int maxIndex = arr.length - 1 - i;
                for (int j = arr.length - 1 - i - 1; j > i; j--) {
                    if (arr[maxIndex] < arr[j]) {
                        maxIndex = j;
                    }
                }
                if (maxIndex != arr.length - 1 - i) {
                    int max = arr[maxIndex];
                    arr[maxIndex] = arr[arr.length - 1 - i];
                    arr[arr.length - 1 - i] = max;
                }
                System.out.println("简单选择排序--改良");
                print(arr, i);
            }
        }
    }

3、快排

  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(logn)
  • 非稳定排序
  • 原地排序
  • 思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
  1. 设置 low=0, high=N-1。
  2. 选择一个基准元素赋值给temp,即temp=a[low]。
  3. 从high开始向前搜索,即由后开始向前搜索(high–),找到第一个小于temp的值,将a[high]和a[low]交换。
  4. 从low开始向前后搜索,即由前开始向后搜索(low++),找到第一个大于temp的值,将a[high]和a[low]交换。
  5. 重复第3步和第4步,直到 low = = high ,3,4步中,若没找到符合条件的值,执行 high-- 或 low++ ,直到找到为止。进行交换时 low和high的位置不变。当low==high时循环结束。

快速排序在序列元素很少时,效率比较低。因此,元素较少时,可选择使用插入排序

代码:

public static void sort(int[] array, int left, int right) {
        if (left >= right) {
            return;
        }
        int mid = partition(array, left, right);
        sort(array, left, mid - 1);
        sort(array, mid + 1, right);
    }

    public static int partition(int[] array, int left, int right) {
        int i = left;
        int j = right;
        int x = array[left];
        while (i < j) {
            while (i < j && array[j] >= x) {
                j--;
            }
            if (i < j) {
                array[i++] = array[j];
            }
            while (i < j && array[i] <= x) {
                i++;
            }
            if (i < j) {
                array[j--] = array[i];
            }
        }
        array[i] = x;
        return i;
    }

优化后的快排:
预排序或者反序或者数组中的数字全是相同的数字的话,就是最坏情况,时间复杂度为O(N)。假如是排好序的数组,用快排重新排序的话,第一个数字的位置不变,那么将要排序的数组全部被划入右区间,时间复杂度达到O(N)。

    public static void sortMajor(int[] array, int left, int right) {
        if (left >= right) {
            return;
        }
        int mid = partitionMajor(array, left, right);
        sortMajor(array, left, mid - 1);
        sortMajor(array, mid + 1, right);
    }
    
    public static int partitionMajor(int[] array, int left, int right) {
        int i = left;
        int j = right;
        ChooseMajorIndex(array,left,right);
        int x = array[left];
        while (i < j) {
            while (i < j && array[j] >= x) {
                j--;
            }
            if (i < j) {
                array[i++] = array[j];
            }
            while (i < j && array[i] <= x) {
                i++;
            }
            if (i < j) {
                array[j--] = array[i];
            }
        }
        array[i] = x;
        return i;
    }

    /**
     * 三数取中,即取三个关键字先进行排序,将中间数作为枢轴, 一般是取左端、右端和中间三个数, 也可以随机选取。
    对于非常大的待排序的序列来说还是不足以保证能够选择出一个好的pivotkey, 因此还有个办法是所谓的九数取中,先从数组中分三次取样,每次取三个数,三个样品各取出中数,然后从这三个中数当中再取出一个中数作为枢轴 。
    **/
    private static void ChooseMajorIndex(int[] array, int low, int high) {
        int mid=low+(high-low)/2;
        if(array[low]>array[high]){
            swap(array,low,high);
        }
        if(array[mid]>array[high]){
            swap(array,mid,high);
        }
        if(array[low]>array[mid]){
            swap(array,low,mid);
        }
    }

    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

4、插入排序

我们在玩打牌的时候,你是怎么整理那些牌的呢?一种简单的方法就是一张一张的来,将每一张牌插入到其他已经有序的牌中的适当位置。当我们给无序数组做排序的时候,为了要插入元素,我们需要腾出空间,将其余所有元素在插入之前都向右移动一位,这种算法我们称之为插入排序。

  • 时间复杂度:O(n2)
  • 空间复杂度:O(1)
  • 稳定排序
  • 原地排序
public static void sort(int[] array){
        if(array!=null){
            int length=array.length;
            if(length>1){
                for (int i=1;i<length;i++){
                    int insertNode=array[i];//待插入的数据
                    int j=i-1;
                    while(j>=0&&insertNode<array[j]){
                        array[j+1]=array[j];// 如果要插入的元素小于第j个元素,就将第j个元素向后移动
                        j--;
                    }
                    array[j+1]=insertNode;// 直到要插入的元素不小于第j个元素,将insertNote插入到数组中
                }
            }
        }
    }

5、希尔排序

希尔排序可以说是插入排序的一种变种。无论是插入排序还是冒泡排序,如果数组的最大值刚好是在第一位,要将它挪到正确的位置就需要 n - 1 次移动。也就是说,原数组的一个元素如果距离它正确的位置很远的话,则需要与相邻元素交换很多次才能到达正确的位置,这样是相对比较花时间了。

希尔排序就是为了加快速度简单地改进了插入排序交换不相邻的元素以对数组的局部进行排序

希尔排序的思想是采用插入排序的方法,先让数组中任意间隔为 h 的元素有序,刚开始 h 的大小可以是 h = n / 2,接着让 h = n / 4,让 h 一直缩小,当 h = 1 时,也就是此时数组中任意间隔为1的元素有序,此时的数组就是有序的了

  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(1)
  • 非稳定排序
  • 原地排序
   public static void sort(int[] arr) {
        int len=arr.length;
        //定义步长
        int gap=len/2;
        while (gap!=0){
            for(int i=gap;i<len;i++){
                int temp=arr[i];
                int j=i;
                //注意这里的条件
                while (j>=gap&&temp<arr[j-gap]){
                    arr[j]=arr[j-gap];
                    j-=gap;
                }
                arr[j]=temp;
            }
            gap/=2;
        }
    }

6、归并排序

利用分治的思想:将一个大的无序数组有序,我们可以把大的数组分成两个,然后对这两个数组分别进行排序,之后在把这两个数组合并成一个有序的数组。由于两个小的数组都是有序的,所以在合并的时候是很快的。

通过递归的方式将大的数组一直分割,直到数组的大小为 1,此时只有一个元素,那么该数组就是有序的了,之后再把两个数组大小为1的合并成一个大小为2的,再把两个大小为2的合并成4的 …… 直到全部小的数组合并起来。

  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(n)
  • 稳定排序
  • 非原地排序

数组的归并

// 归并排序 O(nlogn)
    public void mergeSort(int[] numbers, int from, int to) {
        if (from >= to) {
            return;
        }
        int mid = (to-from)/2+from;
        mergeSort(numbers, from, mid);
        mergeSort(numbers, mid + 1, to);
        merge(numbers, from, mid, to);
    }

    private void merge(int[] array, int from, int mid, int to) {
        int[] tmp = new int[to - from + 1];
        int i = from, j = mid + 1, k = 0;
        while (i <= mid && j <= to) {
            if (array[i] <= array[j]) {
                tmp[k++] = array[i++];
            } else {
                tmp[k++] = array[j++];
            }
        }
        while (i <= mid) {
            tmp[k++] = array[i++];
        }
        while (j <= to) {
            tmp[k++] = array[j++];
        }
        //把tmp中的数据复制到array中
        System.arraycopy(tmp, 0, array, from, to - from + 1);
    }

数组的非递归排序

    // 归并排序,非递归实现(迭代)
    public void sortMergeIteration(int[] nums) {
        int len = 1;// 初始排序数组的长度
        while (len < nums.length) {
            for (int i = 0; i < nums.length; i += len * 2) {
                sortMergeIterationHelper(nums, i, len);
            }
            len *= 2;// 每次将排序数组的长度*2
        }
    }

    /**
     * 辅助函数
     *
     * @param nums  原数组
     * @param start 从start位置开始
     * @param len   本次合并的数组长度
     */
    private void sortMergeIterationHelper(int[] nums, int start, int len) {
        int[] tem = new int[len * 2];
        int i = start;
        int j = start + len;
        int k = 0;
        while (i < start + len && (j < start + len + len && j < nums.length)) {
            tem[k++] = nums[i] < nums[j] ? nums[i++] : nums[j++];
        }
        while (i < start + len && i < nums.length) {// 注意:这里i也可能超出长度
            tem[k++] = nums[i++];
        }
        while (j < start + len + len && j < nums.length) {
            tem[k++] = nums[j++];
        }
        int right = start + len + len;
        int index = 0;
        while (start < nums.length && start < right) {
            nums[start++] = tem[index++];
        }
    }

链表的归并

 /*
    * 单链表的归并排序
    * */
    public ListNode sortList(ListNode head) {

        if(head==null||head.next==null){
            return head;
        }

        ListNode pre=null;
        ListNode slow=head;
        ListNode fast=head;
        while (fast!=null&&fast.next!=null){
            pre=slow;
            slow=slow.next;
            fast=fast.next.next;
        }
        pre.next=null;
        ListNode l1=sortList(head);
        ListNode l2=sortList(slow);
        return merge(l1,l2);
    }

    private ListNode merge(ListNode node1,ListNode node2){

        ListNode newHead=new ListNode(0);
        ListNode tmp=newHead;

        while (node1!=null&&node2!=null){
            if(node1.val<=node2.val){
                tmp.next=node1;
                node1=node1.next;
            }else{
                tmp.next=node2;
                node2=node2.next;
            }
            tmp=tmp.next;
        }
        if(node1!=null){
            tmp.next=node1;
        }
        if(node2!=null){
            tmp.next=node2;
        }
        return newHead.next;
    }

7、堆排序

最大堆通常被用来进行"升序“排序,而最小堆通常被用来进行”降序"排序。

  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(1)
  • 非稳定排序

在第一个元素的索引为 0 的情形中:

  • 性质一:索引为i的左孩子的索引是 (2i+1);
  • 性质二:索引为i的左孩子的索引是 (2i+2);
  • 性质三:索引为i的父结点的索引是 floor((i-1)/2);

堆排序步骤:

  • 首先,把无序数列排成二叉完全树;
  • 然后,从右下角节点 i,其父节点是(i-1)/2开始,对小树调整成最大堆。直到把i=0的树调成最大堆;
  • 最后,把i=0, j=n-1交换位置,得到最大值,放在了n-1位置上,然后调整i=0的堆,在输出次大值…
//大顶堆,从小到大排序(数列构成大顶堆后,把堆顶元素放到最后一个位置n-1,依次调整,直到堆只剩下一个元素,则数列中的数字,从小到大排序了)
    public void sort(int[] arr) {
        //调整为大顶堆
        buildMaxHeap(arr);
        //在大顶堆中,进行排序
        sortInMaxheapAsc(arr);
    }

    //构建堆,从右下角有孩子的节点开始,调整
    private void buildMaxHeap(int[] arr) {
        int len = arr.length;
        for (int i = (len - 2) / 2; i >= 0; i--) {
            heapify(arr, i,len);
        }
    }
    
    //调整堆:大顶堆
    private void heapify(int[] arr, int i,int len) {
        int maxIndex = i;
        int left = i * 2 + 1;
        int right = i * 2 + 2;
        if (left < len && arr[maxIndex] < arr[left])
            maxIndex = left;
        if (right < len && arr[maxIndex] < arr[right])
            maxIndex = right;
        if (maxIndex != i) {
            swap(arr, i, maxIndex);
            heapify(arr, maxIndex,len);
        }
    }

    //在大顶堆中,进行排序,之后调整
    public void sortInMaxheapAsc(int[] arr) {
        int len=arr.length;
        for (int i = arr.length - 1; i > 0; i--) {
            swap(arr, 0, i);
            len--;
            heapify(arr, 0,len);
        }
    }

    private void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

8、topK(变相堆排序)

public class HeapSortTopK {

    public static void main(String[] args) {
        int[] array=new int[]{6,5,4,3,2,1,0};

        System.out.println("堆排序TopK之前:");
        for (int i = 0; i < array.length; i++) {
            System.out.print(" " + array[i]);
        }
        System.out.println();

        HeapSortTopK heapSortTopK = new HeapSortTopK();
        int[] heap = heapSortTopK.getMinTopK(array, 5);

        System.out.println("堆排序TopK之后:");
        for (int i = 0; i < heap.length; i++) {
            System.out.print(" " + heap[i]);
        }
    }

    /*
    * 找到数组中的前k个最小的数
    * */
    public int[] getMinTopK(int[] array, int k) {
        //把结果放进heap中
        int[] heap = new int[k];
        if (array == null || array.length == 0 || k <= 0 || array.length < k) {
            return null;
        }

        //向上调整
        for (int i = 0; i < k; i++) {
            insert(heap, i, array[i]);
        }

        //向下调整
        for (int i = k; i < array.length; i++) {
            if (array[i] < heap[0]) {
                heap[0]=array[i];
                heapify(heap,0,heap.length);
            }
        }

        System.out.println("堆排序TopK之中:");
        for (int i = 0; i < heap.length; i++) {
            System.out.print(" " + heap[i]);
        }
        System.out.println();

        //经过上面的步骤,得到了最小的K个数,构成的最大堆,下面通过堆排序,把最大堆中的数字,从小到大一次输出
        sortInMaxheapAsc(heap);
        return heap;
    }

    public void sortInMaxheapAsc(int[] arr) {
        int len=arr.length;
        for (int i = arr.length - 1; i > 0; i--) {
            swap(arr, 0, i);
            len--;
            heapify(arr, 0,len);
        }
    }

    //调整堆:大顶堆,向下调整
    private void heapify(int[] arr, int i,int len) {
        int maxIndex = i;
        int left = i * 2 + 1;
        int right = i * 2 + 2;
        if (left < len && arr[maxIndex] < arr[left])
            maxIndex = left;
        if (right < len && arr[maxIndex] < arr[right])
            maxIndex = right;
        if (maxIndex != i) {
            swap(arr, i, maxIndex);
            heapify(arr, maxIndex,len);
        }
    }

    private void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    // 大顶堆:向上调整
    private void insert(int[] heap, int index, int num) {
        heap[index] = num;
        //向上调整
        while (index != 0) {
            int father = (index - 1) / 2;
            if (heap[father] > heap[index]) {
                break;
            } else {
                int temp = heap[index];
                heap[index] = heap[father];
                heap[father] = temp;
                index = father;
            }
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值