数据结构-排序算法总结

数据结构中排序算法是各大公司面试的常客,下面则是对各种排序算法的总结:

基本概念

1、排序:排序是计算机内经常进行的一种操作,其目的是将一组“无序”的记录序列调整为“有序”的记录序列

2、内部排序和外部排序:整个排序过程完全在内存中进行,叫做内部排序。数据量较大需要借助外部存储设备才能完成,叫做外部排序。

3、时间复杂度:算法中基本操作重复执行的次数称为算法的时间复杂度,记为 T(n)=O(f(n))

4、空间复杂度:算法所需存储空间的量度称为算法的空间复杂度,记为 S(n)=O(f(n)) ,若排序算法所需的辅助空间并不依赖于问题的规模n,即辅助空间为 O(1)

5、排序的稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中, ri=rj ,且 ri rj 之前,而在排序后的序列中, ri 仍在 rj 之前,则称这种排序算法是稳定的;否则称为不稳定的。

算法分类

排序算法总共可以分4大类,分别是插入类排序、交换类排序、选择类排序、归并类排序。各种排序算法的分类以及时间复杂度和空间复杂度的对比图如下:

这里写图片描述

下面将对各种排序算法逐一进行叙述:

一、插入类排序

思想:在一个已经排好序的序列中,将未被排进的元素按照原先的规定插入到指定位置。

1、 直接插入排序:

思想:最基本的插入排序,将第 i 个元素插入到前i1个元素中的适当位置。

时间复杂度: T(n)=O(n2)

空间复杂度: S(n)=O(1)

稳定性:稳定排序

Java实现:

    // 直接插入排序
    // 时间复杂度:T(n)=O(n^2)
    // 空间复杂度:S(n) = O(1)
    // 稳定性:稳定排序
    public void directInsertSort(int[] arr){
      int temp; //哨兵
      int i,j;
      for(i = 1;i < arr.length;i++){
          if(arr[i] < arr[i-1]){
              temp = arr[i];
              for( j = i-1;j >= 0 && arr[j] > temp;j--)
                  arr[j+1] = arr[j];
              arr[j+1] = temp;
          }
      }
    }

2、 折半插入排序:

思想:折半插入排序是直接插入排序的改进版,由于前半部分为已排好序的数列,这样我们不用按顺序依次寻找插入点,可以采用折半查找的方法来加快寻找插入点的速度。由于折半查找只是减少了比较次数,但是元素的移动次数不变,因此时间复杂度和直接插入排序一样大。

时间复杂度: T(n)=O(n2)

空间复杂度: S(n)=O(1)

稳定性:稳定排序

Java实现:

    // 折半插入排序
    // 时间复杂度:T(n)=O(n^2)
    // 空间复杂度:S(n) = O(1)
    // 稳定性:稳定排序
    public void binaryInsertSort(int[] arr) {
        int length = arr.length;
        int temp;
        for(int i = 1;i < length;i++){
            int midIndex = (0 + i-1)/2;
            if(arr[i] < arr[midIndex]){
                for(int j = 0;j <= midIndex;j++){
                    if(arr[i] < arr[j]){
                        temp   = arr[i];
                        arr[i] = arr[j];
                        arr[j] = temp;
                    }
                }
            }
            else {
                for (int j = midIndex; j < i; j++) {
                    if (arr[i] < arr[j]) {
                        temp = arr[i];
                        arr[i] = arr[j];
                        arr[j] = temp;
                    }
                }
            }
        }
    }

3、 希尔排序:

思想:希尔排序是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序把待排序序列分成若干较小的子序列,然后逐个使用直接插入排序法排序,最后再对一个较为有序的序列进行一次排序,主要是为了减少移动的次数,提高效率。

时间复杂度: T(n)=O(n32)

空间复杂度: S(n)=O(1)

稳定性:不稳定排序

Java实现:

    // 希尔排序
    // 时间复杂度:T(n)=O(n^1.5)
    // 空间复杂度:S(n) = O(1)
    // 稳定性:不稳定排序
    public void shellSort(int[] arr) {
        int temp,i,j;
        //增量gap,并逐步缩小
        // 增量
        for(int gap = arr.length/2;gap > 0;gap /= 2){
        //从第gap个元素,逐个对其所在组进行直接插入排序操作
            for(i = gap;i < arr.length;i++){
                for(j = i-gap;j >= 0 && arr[j] > arr[j+gap];j -= gap){
                   temp = arr[j];
                   arr[j] = arr[j+gap];
                    arr[j+gap] = temp;
               }
            }
        }
    }

二、交换类排序

思想:两两比较待排序记录的关键字,发现两记录的次序相反时即进行交换,直到没有反序的记录为止。

1、 冒泡排序:

思想:冒泡排序将待排序的元素看作是竖着排列的“气泡”,较小的元素比较轻,从而要往上浮。

时间复杂度: T(n)=O(n2)

空间复杂度: S(n)=O(1)

稳定性:稳定排序

Java实现:

    // 冒泡排序
    // 时间复杂度:T(n)=O(n^2)
    // 空间复杂度:S(n) = O(1)
    // 稳定性:稳定排序
    public void bubbleSort(int[] arr) {
        int length = arr.length;
        int temp;
        for(int i = 0;i < length;i++){
            for(int j = 1;j < length;j++){
                if(arr[j] < arr[j-1]){
                    temp     = arr[j];
                    arr[j]   = arr[j-1];
                    arr[j-1] = temp;
                }
            }
        }
    }

2、 快速排序:

思想:冒泡排序一次只能消除一个逆序,为了能一次消除多个逆序,采用快速排序,因此快速排序是冒泡排序的一种改进。通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。首先在序列中随机选取一个数字,将大于该数字的放在其右边,小于该数字的放在左边。对于该数字左、右边的序列递归调用该过程。

时间复杂度: T(n)=O(nlog(n))

空间复杂度: S(n)=O(log(n))

稳定性:不稳定排序

Java实现:

  /*
    // 快速排序
    // 时间复杂度:T(n)=O(nlogn)
    // 空间复杂度:S(n) = O(logn)
    // 稳定性:不稳定排序
     */
    void quickSort(int arr[], int start, int end) {
        if(start < end){//判断start是否等于end,如果等于则说明各区间只有一个数
            int i = start;
            int j = end;
            int x = arr[start];//将start作为基准数
            while(i < j){//如果i=j,则结束
                while(i < j && arr[j] >= x) // 从右向左找第一个小于x的数
                    j--;
                //此时,arr[j] < x,则令arr[i]=arr[j],且i向前移动一格
                if(i < j)
                    arr[i++] = arr[j];

                while(i < j && arr[i] < x)// 从左向右找第一个大于等于x的数
                    i++;
                //此时,arr[i] >= x,则令arr[j]=arr[i],且j向后移动一格
                if(i < j)
                    arr[j--] = arr[i];
            }
            //此时,arr[i]=arr[j]=x,且比x大的数全在它的右边,小于或等于它的数全在其左边。下面则再对其左右区间重复这个步骤
            arr[i] = x;
            quickSort(arr,start,i-1);//对其左边区间重复上述操作
            quickSort(arr,i+1,end);//对其右边区间重复上述操作
        }
    }

三、选择类排序

思想:选择排序的基本思想是对待排序的记录序列进行n-1遍的处理,第i遍处理是将L[i..n]中最小者与L[i]交换位置。

1、 简单选择排序:

思想:依次遍历序列,每次选出值最小的记录,并和当前所遍历的下标最小的记录进行对换。

时间复杂度: T(n)=O(n2)

空间复杂度: S(n)=O(1)

稳定性:稳定排序

Java实现:

 /*
    *简单选择排序
    * // 时间复杂度:T(n)=O(n^2)
      // 空间复杂度:S(n) = O(1)
      // 稳定性:稳定排序
     */
    void selectSort(int arr[]) {
        int length = arr.length;
        int temp;
        for(int i = 0;i < length;i++){
            int min = arr[i];
            for(int j = i;j < length;j++){
                if(min > arr[j]){
                    temp = min;
                    min = arr[j];
                    arr[j] = temp;
                }
            }
            arr[i] = min;
        }
    }

2、 堆排序:

思想:堆排序与快速排序,归并排序一样都是时间复杂度为 O(n*logn)的排序方法。想明白什么是堆排序,得先明白什么是二叉堆:
二叉堆是完全二叉树或者是近似完全二叉树。 二叉堆满足二个特性:
1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。
当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆。当父结点的 键值总是小于或等于任何一个子节点的键值时为最小堆。
一般都用数组来表示堆,i 结点的父结点下标就为(i – 1) / 2。它的左右子结点下 标分别为 2 * i + 1 和 2 * i + 2。

这里写图片描述

堆的插入删除

这里写图片描述

由于堆也是用数组模拟的,由于堆的根节点是最小的,故堆化数组后,第一次将A[0]与A[n - 1]交换,再对A[0…n-2]重新恢复堆。第二次将A[0]与A[n – 2]交换,再对A[0…n - 3]重新恢复堆,重复这样的操作直到A[0]与A[1]交换。由于每次都是将最小的数据并入到后面的有序区间,故操作完成后整个数组就有序了。

时间复杂度: T(n)=O(nlogn)

空间复杂度: S(n)=O(1)

稳定性:不稳定排序

Java实现:

     /**
     * 堆排序
     */
    void heapSort(int[] array) {
        if (array == null || array.length <= 1) {
            return;
        }
        buildMaxHeap(array);
        for (int i = array.length - 1; i >= 0; i--) {
            int temp = array[0];
            array[0] = array[i];
            array[i] = temp;
            maxHeap(array, i, 0);
        }
    }

    /**
     * 创建最大堆
     */
    void buildMaxHeap(int[] array) {
        if (array == null || array.length <= 1) {
            return;
        }
        for (int i = array.length / 2; i >= 0; i--) {
            maxHeap(array, array.length, i);
        }
    }

    /**
     * 调整最大堆
     */
    void maxHeap(int[] array, int heapSize, int index) {
        int left = index * 2 + 1;
        int right = index * 2 + 2;

        int largest = index;
        if (left < heapSize && array[left] > array[index]) {
            largest = left;
        }

        if (right < heapSize && array[right] > array[largest]) {
            largest = right;
        }

        if (index != largest) {
            int temp = array[index];
            array[index] = array[largest];
            array[largest] = temp;
            maxHeap(array, heapSize, largest);
        }
    }

四、归并排序:

1、 归并排序:

思想:该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序,其的基本思路就是将数组分成二组A,B,如果这二组组内的数据都是有序的,那么就可以很方便的将这二组数据进行排序。如何让这二组组内数据有序了?可以将A,B组各自再分成二组。依次类推,当分出来的小组只有一个数据时,可以认为这个小组的组内已经达到了有序,然后再合并相邻的二个小组就可以了。这样通过先递归的分解数列,再合并数列就完成了归并排序。

时间复杂度: T(n)=O(nlogn)

空间复杂度: S(n)=O(n)

稳定性:稳定排序

Java实现:

   /**
     * 归并排序
     * 时间复杂度:T(n)=O(nlogn)
     * 空间复杂度:S(n) = O(n)
     * 稳定性:稳定排序
     */
   void mergeSort(int[] nums, int low, int high) {
        int mid = (low + high) / 2;
        if (low < high) {
            // 左边
            mergeSort(nums, low, mid);
            // 右边
            mergeSort(nums, mid + 1, high);
            // 左右归并
            merge(nums, low, mid, high);
        }
    }

    void merge(int[] nums, int low, int mid, int high) {
        int[] temp = new int[high - low + 1];
        int i = low;// 左指针
        int j = mid + 1;// 右指针
        int k = 0;

        // 把较小的数先移到新数组中
        while (i <= mid && j <= high) {
            if (nums[i] < nums[j]) {
                temp[k++] = nums[i++];
            } else {
                temp[k++] = nums[j++];
            }
        }

        // 把左边剩余的数移入数组
        while (i <= mid) {
            temp[k++] = nums[i++];
        }

        // 把右边边剩余的数移入数组
        while (j <= high) {
            temp[k++] = nums[j++];
        }

        // 把新数组中的数覆盖nums数组
        for (int k2 = 0; k2 < temp.length; k2++) {
            nums[k2 + low] = temp[k2];
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值