排序算法

最经典的、最常用的排序算法:

1. 时间复杂度O(n2),空间复杂度O(1) :冒泡排序、插入排序、选择排序

2. 时间复杂度O(nlogn) :归并排序、快速排、堆排序

3. 时间复杂度O(n) :计数排序、基数排序、桶排序

1.1 冒泡排序

它会遍历若干次要排序的数列,每次遍历时,它都会从前往后依次的比较相邻两个数的大小;如果前者比后者大,则交换它们的位置。这样,一次遍历之后,最大的元素就在数列的末尾! 采用相同的方法再次遍历时,第二大的元素就被排列在最大元素之前。重复此操作,直到整个数列都有序为止!

   private int[] bubbleSort(int [] a) {
        for (int i = 0; i < a.length; i++){
            // 提前退出冒泡循环的标志位
            boolean flag = false;
            for (int j = 0; j < a.length - i -1; j++) {
                if (a[j] > a[j+1]) {
                    a[j] ^= a[j+1];
                    a[j+1] ^= a[j];
                    a[j] ^= a[j+1];
                    flag = true;
                }
            }
            //没有交换,则说明已经排好序了
            if (!flag) break;
        }
        return a;
    }

1.2  插入排序

首先,我们将数组中的数据分为两个区间,已排序区间和未排序区间。初始已排序区间只有一个元素,就是数组的第一个元素。插入算法的核心思想是每次取未排序区间中的一个元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序。重复这个过程,直到遍历完无序数据。

插入排序也包含两种操作,一种是元素的比较,一种是元素的移动。当我们需要将一个数据 a 插入到已排序区间时,需要拿 a 与已排序区间的元素依次比较大小,找到合适的插入位置。找到插入点之后,我们还需要将插入点之后的元素顺序往后移动一位,这样才能腾出位置给元素 a 插入。

插入排序每次遍历只需要执行一个赋值语句(移动数据时), 而冒泡排序需要三个赋值语句,因此插入排序性能更好;

    private int[] insertSort(int [] a) {
        if (a.length <= 1) return a;
       for (int i = 1; i < a.length; i++) {
           int value  = a[i];
           int j = i-1;
           //从后往前比较
           for (; j >= 0; j--) {
               if (a[j] > value) {
                   a[j+1] = a[j];//移动数据
               } else {
                   break;
               }
           }
           //插入数据
           a[j+1] = value;
       }
        return a;
    }

1.3 选择排序

选择排序也是先将数据分为已排序区间和未排序区间。然后每次从未排序区间中找到最小的元素,将其放到已排序区间的末尾。

    private int[] selectSort(int [] a) {
        if (a.length <= 1) return a;
        int minIndex;
       for (int i = 0; i < a.length; i++) {
           minIndex  = i;
           for (int j = i+1; j < a.length; j++) {
               if (a[j] < a[minIndex]) {
                   minIndex = j;
               }
           }
           //将最小值换到有序区间的末尾
           if (minIndex != i) {
               a[minIndex] ^= a[i];
               a[i] ^= a[minIndex];
               a[minIndex] ^= a[i];
           }
       }
        return a;
    }

 

1.4希尔排序

希尔排序(Shell Sort)是插入排序的一种,它是针对直接插入排序算法的改进。该方法又称缩小增量排序。

希尔排序实质上是一种分组插入方法。它的基本思想是:对于n个待排序的数列,取一个小于n的整数gap(gap被称为步长)将待排序元素分成若干个组子序列,所有距离为gap的倍数的记录放在同一个组中;然后,对各组内的元素进行直接插入排序。 这一趟排序完成之后,每一个组的元素都是有序的。然后减小gap的值,并重复执行上述的分组和排序。重复这样的操作,当gap=1时,整个数列就是有序的。

//希尔排序
    private int[] shellSort(int[] a) {
        int m, i, j, k, gap;
        int n = a.length;
        if (n < 1) return a;
        //循环直到gap为1
        for (gap = n / 3 + 1; gap >= 1; gap = gap / 3 + 1) {
            //将整个数组分成gap个组,对每个组分别进行插入排序
            for (m = 0; m < gap; m++) {
                //插入排序,步长为gap
                for (i = m + gap; i < n; i += gap) {
                    k = a[i];
                    for (j = i - gap; j >= 0 && k < a[j]; j -= gap) {
                        a[j + gap] = a[j];
                    }
                    a[j + gap] = k;
                }
            }
            if (gap == 1) {
                break;
            }
        }
        return a;
    }

2.1 归并排序

归并排序使用的是分治思想,递归的方式;我们先把数组从中间分成前后两部分,然后对前后两部分分别排序,再将排好序的两部分合并在一起,用递归的方式对子序列再进行分割,合并,直到区间内只剩一个元素;每次递归结束都得到一个有序的子序列;最终整个数组就都有序了。这是因为归并排序的合并函数,在合并两个子区间时,需要借助额外的存储空间, 因此空间复杂度是 O(n)。

    private int[] mergeSort(int[]a, int p, int q) {
        if (p >= q) {
            return a;
        }
        int m = (q + p) /2;
        mergeSort(a, p, m);
        mergeSort(a, m+1, q);
        merge(a, p, m, q);
        return a;
    }

    private int[] merge(int[] a, int start, int mid, int end) {
        int[] temp = new int[end - start +1]; //临时数组
        int i = start;
        int j = mid+1;
        int k = 0; //临时数组索引
        while (i <= mid && j <= end) {
            if (a[j] > a[i]){
                temp[k++] = a[i++];
            } else {
                temp[k++] = a[j++];
            }
        }

        while (i <= mid) {
            temp[k++] = a[i++];
        }

        while (j <= end) {
            temp[k++] = a[j++];
        }
        for (int i1 = 0; i1 < temp.length; i1++) {
            a[start++] = temp[i1];
        }

        return a;
    }

2.2 快速排序

选择一个基准数,通过一趟排序将要排序的数据分割成独立的两部分;其中一部分的所有数据都比另外一部分的所有数据都要小。然后,再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

如果要排序数组中下标从 p 到 r 之间的一组数据,我们选择 p 到 r 之间的任意一个数据作为 pivot(分区点)。我们遍历 p 到 r 之间的数据,将小于 pivot 的放到左边,将大于 pivot 的放到右边,将 pivot 放到中间。经过这一步骤之后,数组 p 到 r 之间的数据就被分成了三个部分,前面 p 到 q-1 之间都是小于 pivot 的,中间是 pivot,后面的 q+1 到 r 之间是大于 pivot 的。根据分治、递归的处理思想,我们可以用递归排序下标从 p 到 q-1 之间的数据和下标从 q+1 到 r 之间的数据,直到区间缩小为 1,就说明所有的数据都有序了。

    private int[] quickSort(int[]a, int start, int end) {
        if (start >= end) {
            return a;
        }
        int q = partition(a, start, end); //获取分区点
        quickSort(a, start, q-1);
        quickSort(a, q+1, end);

        return a;
    }

    private int partition(int[] a, int start, int end) {
        int i = start;
        int j = start;
        int pivot = a[end];
        while (j < end) {
            if (a[j] < pivot) {
                int temp = a[j];
                a[j] = a[i];
                a[i] = temp;
                i++;
            }
            j++;
        }
        int temp = a[end];
        a[end] = a[i];
        a[i] = temp;
        return i;
    }

 2.3 堆排序

堆排序(Heap Sort)是指利用堆这种数据结构所设计的一种排序算法。
因此,学习堆排序之前,有必要了解堆

我们知道,堆分为"最大堆"和"最小堆"。最大堆通常被用来进行"升序"排序,而最小堆通常被用来进行"降序"排序。
最大堆进行升序排序的基本思想:
① 建堆:将数列a[1...n]构造成最大堆。
② 排序:将a[1]和a[n]交换,使a[n]是a[1...n]中的最大值;然后将a[1...n-1]重新调整为最大堆。 接着,将a[1]和a[n-1]交换,使a[n-1]是a[1...n-1]中的最大值;然后将a[1...n-2]重新调整为最大值。 依次类推,直到整个数列都是有序的。

    //堆排序
    private int[] heapSort(int[] a) {
        int n = a.length -1;//最后一个元素下标
        //1.建堆
        buildHeap(a, n);
        //2.排序
        while (n>0) {
            //把堆顶元素和和数组最后的元素交换,数组最后一个元素就是最大值
            swap(a, 0, n);
            n--;
            //将前n-1个元素再构造成最大堆
            heapify(a, 0, n);
        }
        return a;
    }

    //数组下标从0开始,左子节点下标2*i+1,右子节点下标2*i+2
    //构造大顶推
    private void buildHeap(int[]a, int n) {
        int i = (n-1)/2; //从最后一个非叶子节点开始(数组最后一个元素的父节点),从后往前遍历
        while(i>=0) {
            heapify(a, i, n);
            i--;
        }
    }

    //从上往下堆化
    private void heapify(int[]a, int i, int n) {
        int maxPos = i;
        while(true) {
            if (2*i+1 <= n && a[i] < a[2*i+1]) maxPos = 2*i+1;
            if (2*i+2 <= n && a[maxPos] < a[2*i+2]) maxPos = 2*i+2;
            if (maxPos == i) break; //没有子节点了
            swap(a, i, maxPos);
            i = maxPos;
        }
    }

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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值