数据结构-排序算法总结

来源:互联网 发布:图像领域的经典算法 编辑:程序博客网 时间:2024/05/22 03:51

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

基本概念

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

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

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

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

5、排序的稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,ri=rj,且rirj之前,而在排序后的序列中,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];        }    }
原创粉丝点击