美文网首页
PTA刷题总结-Part3.3 排序专题

PTA刷题总结-Part3.3 排序专题

作者: 苏wisdom | 来源:发表于2020-04-05 18:44 被阅读0次

    本章节涵盖了冒泡排序O(n^2) 、选择排序O(n^2) 、插入排序O(n^2)、快速排序O(nlogn)和归并排序O(nlogn)的内容。

    其中工程上用得最多的是插入排序和快速排序。而在写算法题的时候简单排序可以考虑直接使用库函数sort(首元素地址,尾元素地址的下一个地址,比较函数),其中最后一个参数“比较函数”非必填。

    比如针对int a[] = {3,2,1,4}, 想要升序排序下标[0,2]这三个元素,那么需要调用sort(a, a+3)

    #include <algorithm>
    #include <vector>
    #include <stdio.h>
    using namespace std;
    
    vector<int> Arr;
    
    bool cmp(int a, int b){
        return a>b;
    }
    int main(){
        Arr.push_back(3);
        Arr.push_back(1);
        Arr.push_back(2);
        sort(Arr.begin(), Arr.end());
        for (int i=0; i<Arr.size();i++){
            printf("%d ", Arr[i]);
        }
        printf("\n");
        sort(Arr.begin(), Arr.end(),cmp);
        for (int i=0; i<Arr.size();i++){
            printf("%d ", Arr[i]);
        }
        printf("\n");
        int a[8] = {2,4,5,1,3,9,4,-1};
        sort(a, a+7);
        for (int i=0; i<8;i++){
            printf("%d ", a[i]);
        }
    }
    
    

    输出结果依次是
    1 2 3
    3 2 1
    1 2 3 4 4 5 9 -1

    1 冒泡排序O(n^2),稳定

    当题目告诉你n的规模超过1000的话,就不能用了。

    道理很简单,针对递增排序来说,对数组一共会做n-1趟扫描(i=1;i<=n-1;i++),每次扫描的时候,比较的都是相邻两个数的大小(a[j] > a[j+1]),然后做交换。每趟结束的时候,本趟扫描中的最大值会像气泡一样排到了末尾位置。每趟能够扫描到的数是(j=0;j<n-i;j++)。

    • 稳定
    • 做交换的次数 = 逆序对数
    • 最好O(N)
    • 平均O(n^2)
    • 空间O(1)
    • 内部排序,原地排序

    题目:7-73 比较大小 (10分)

    #include <stdio.h>
    void swap(int *a, int *b){
        int temp = *a;
        *a = *b;
        *b = temp;
    }
    int main() {
        int num[3] = {0};
        for (int i=0;i<3;i++){
            scanf("%d", &num[i]);
        }
        for (int i=1;i<=2;i++){
            for (int j=0;j<3-i;j++){
                if (num[j] > num[j+1]){ // 相等的时候不做交换,所以稳定
                    swap(&num[j], &num[j+1]);
                }
            }
        }
        printf("%d", num[0]);
        for (int i=1;i<3;i++){
            printf("->%d", num[i]);
        }
        return 0;
    }
    

    如果扫描一趟就发现已经是递增序列了,就不继续扫描了

    #include <stdio.h>
    
    void sort ( int a[], int len );
    
    int main(void)
    {
        int n;
        scanf("%d", &n);
        int a[n];
        for ( int i=0; i<n; i++ ) {
            scanf("%d", &a[i]);
        }
        
        sort(a, n);
    
        for ( int i=0; i<n; i++ ) {
            printf("%d\n", a[i]);
        }
    }
    
    /* 请在这里填写答案 */
    void sort ( int a[], int len ){
        int is_sort = 1;
        for (int i = len-1;i>0;i--){
            is_sort = 1;
            for (int j = 0;j<i;j++){
                if (a[j] > a[j+1]){
                    is_sort = 0;
                    int temp = a[j];
                    a[j] = a[j+1];
                    a[j+1] = temp;
                }
            }
            if (is_sort) {
                break;
            }
        }
    }
    

    2 选择排序 O(n^2)

    首先在未排序序列中找到最小元素,存放到排序序列的起始位置。
    再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
    重复第二步,直到所有元素均排序完毕。

    同样是O(n^2)的算法复杂度,同样无法处理n的规模超过1000的无序数据。但是冒泡排序针对已经排序好的序列,有O(n)复杂度的优势。

    • 不稳定
    • 无论什么情况下,都要比较N*(N-1)/2
    • 最好O(n^2)
    • 平均O(n^2)
    • 空间O(1)
    • 内部排序,原地排序
    #include <stdio.h>
    
    void sort ( int a[], int len );
    
    int main(void)
    {
        int n;
        scanf("%d", &n);
        int a[n];
        for ( int i=0; i<n; i++ ) {
            scanf("%d", &a[i]);
        }
        
        sort(a, n);
    
        for ( int i=0; i<n; i++ ) {
            printf("%d\n", a[i]);
        }
    }
    
    /* 请在这里填写答案 */
    void sort ( int a[], int len ){
        for (int i=0;i<len;i++){
            int min = i;
            for (int j = i+1;j<len;j++){
                // 找到无序数组中的最小值 
                if (a[j] < a[min]){
                    min = j;
                }
            }
            if (min != i){
                int temp = a[i];
                a[i] = a[min];
                a[min] = temp;
            }
        }
    }
    

    3 直接插入排序 O(n^2)

    插入排序也可以引入二分查找进行优化,或者使用每次交换相隔一定元素的希尔排序。但是这里说的是普通的直接插入排序,或者叫简单插入排序。

    插入排序假定左侧都是有序的,比如左侧都是升序[0,2,4,5], 右侧是等待排序的序列,比如[1,7], 那么1就会从数字5开始,逐一向左比较,发现数字比1大,就发生交换(右移动),一直到与数字0比较发现小于1为止,然后把1放到0后面的位置。这个过程就像打扑克牌时的摸牌一样。

    简单插入排序效率不高的主要原因是每次只移动相邻的两个元素,这样只能消去一对错位的元素。希尔排序的改进方案是,每次交换相隔一定距离的元素,比如先相隔5,再相隔3,最后相隔1即使用简单插入排序,到最后相隔1的时候,前面的步骤已经交换了很多逆序对。希尔排序是不稳定的,时间复杂度的计算也很复杂。

    针对已经排好序的大规模数据,插入排序的表现也是比选择排序要好,接近O(N),而且我们使用不交换数据直接赋值的方式进行数据的插入,这也比冒泡排序一个个相邻元素都交换有优势。所以冒泡和选择一般停留在理论上,工程上插入排序是用得更多的。

    • 稳定
    • 做交换的次数 = 逆序对数
    • 最好O(N) 已经有序
    • 平均O(N^2)
    • 空间O(1)
    • 内部排序,原地排序
    #include <stdio.h>
    
    void sort ( int a[], int len );
    
    int main(void)
    {
        int n;
        scanf("%d", &n);
        int a[n];
        for ( int i=0; i<n; i++ ) {
            scanf("%d", &a[i]);
        }
    
        sort(a, n);
    
        for ( int i=0; i<n; i++ ) {
            printf("%d\n", a[i]);
        }
    }
    
    void sort ( int a[], int len ){
        for (int i=1;i<len;i++){
            int value=a[i];
            int j=i;
            while (j>0 && a[j-1] > value){
                a[j] = a[j-1];
                j--;
            }
            a[j] = value;
        }
    }
    
    

    4 快速排序 O(NlogN),不稳定

    注意对于有序数组,使用快排仍然是O(n^2)时间复杂度,也就是说对于n规模大于1000且已知测试点序列可能有序的情况下,就不要使用快排了

    https://www.lintcode.com/problem/sort-integers-ii/description

    public class Solution {
        /**
         * @param A: an integer array
         * @return: nothing
         */
        public void sortIntegers2(int[] A) {
            if (A == null || A.length == 0) {
                return;
            }
            quickSort(A, 0, A.length - 1);
        }
        
        private void quickSort(int[] A, int start, int end) {
            // 已经排好序了
            if (start >= end) {
                return;
            }
            
            int left = start, right = end;
            // 获取中间的pivot,是数组值,不是下标
            int pivot = A[(start + (end - start) / 2)];
            // 所有都是left <= right,保证扫描结束两根指针不重叠,而是right在左,left在右。可能相邻,也可能相隔一个数
            while (left <= right) {
                // 数组中的值使用A[left] < pivot,不使用相等,保证相等的数均分
                while(left <= right && A[left] < pivot) {
                    left++;
                }
                while(left <= right && A[right] > pivot) {
                    right--;
                }
                if (left <= right) {
                    int temp = A[left];
                    A[left] = A[right];
                    A[right] = temp;
                    left++;
                    right--;
                }
            }
            // 最后right会在left左边
            quickSort(A, start, right);
            quickSort(A, left, end);
        }
    }
    

    当序列中的元素接近有序时,这种选择pivot的方法时时间复杂度会退化成O(n^2)。

    找到第K大的数/中位数

    https://www.lintcode.com/problem/kth-largest-element/description

    public class Solution {
        /**
         * @param n: An integer
         * @param nums: An array
         * @return: the Kth largest element
         */
        public int kthLargestElement(int n, int[] nums) {
            if (nums == null || nums.length == 0) {
                return -1;
            }
            return findKthLargest(nums, 0, nums.length - 1, n);
        }
        
        private int findKthLargest(int[] nums, int start, int end, int k) {
            if (start >= end) {
                return nums[start];
            }
            
            int left = start, right = end;
            int pivot = nums[(start + (end - start) / 2)];
            while (left <= right) {
                while (left <= right && nums[left] > pivot) {
                    left++;
                }
                while (left <= right && nums[right] < pivot) {
                    right--;
                }
                if (left <= right) {
                    int temp = nums[left];
                    nums[left] = nums[right];
                    nums[right] = temp;
                    left++;
                    right--;
                }
            }
            
            if (start + k - 1 <= right) {
                return findKthLargest(nums, start, right, k);
            }
            if (start + k - 1 >= left) {
                return findKthLargest(nums, left, end, k - (left - start));
            }
            return nums[right+1];
        }
    }
    

    5 归并排序 O(NlogN),稳定

    对于序列[1,2,3,1,4] 5个数

    • 第一趟merge下标0~1 得到[1,2]
    • 第二趟merge下标0~2 得到[1,2,3]
    • 第三趟merge下标3~4 得到[1,4]
    • 第四趟merge下标0~4 得到[1,1,2,3,4]

    归并排序需要开辟一个临时数组,所以这个数组不能定义在方法中,内存不够。

    想象2G的内存,排序2G的数据,如果使用归并排序的话,只有1G的内存可以用。归并排序一般不用于内部排序,是外部排序的有用工具。

    #include <stdio.h>
    
    void sort ( int a[], int len );
    
    int main(void)
    {
        int n;
        scanf("%d", &n);
        int a[n];
        for ( int i=0; i<n; i++ ) {
            scanf("%d", &a[i]);
        }
        
        sort(a, n);
    
        for ( int i=0; i<n; i++ ) {
            printf("%d\n", a[i]);
        }
    }
    
    int temp[1000000];
    void mergeSort(int a[],int left,int right);
    void merge(int a[],int l1,int r1,int l2,int r2); // [l1,r1] 和 [l2,r2] 
    
    
    void sort ( int a[], int len ){
        mergeSort(a,0,len-1);
    }
    void mergeSort(int a[],int left,int right){
        if (left<right){
            int mid = (left+right) >>1;
            mergeSort(a,left,mid);
            mergeSort(a,mid+1,right);
            merge(a,left,mid,mid+1,right);
        }
    }
    void merge(int a[],int l1,int r1,int l2,int r2){
        int i=l1,j=l2;
        int index = 0;
        while (i<=r1 && j<= r2){
            if (a[i] <= a[j]){
                temp[index++] = a[i++];
            } else {
                temp[index++] = a[j++];
            }
        }
        // 到此处时,要么i已经等于r1, 要么j已经到达r2 
        while (i<=r1){
            temp[index++] = a[i++];
        }
        while (j<=r2){
            temp[index++] = a[j++];
        }
        for (i = 0;i<index;i++){
            a[l1+i] = temp[i];
        }
    }
    
    public class Solution {
        /**
         * @param A: an integer array
         * @return: nothing
         */
        public void sortIntegers2(int[] A) {
            if (A == null || A.length == 0) {
                return;
            }
            int temp[] = new int[A.length];
            mergeSort(A, 0, A.length - 1, temp);
        }
        private void mergeSort(int[] A, int left, int right, int[] temp){
            if (left < right) {
                int mid = left + (right - left) / 2;
                mergeSort(A, left, mid, temp);
                mergeSort(A, mid+1, right, temp);
                merge(A, left, mid, mid + 1, right, temp);
            }
        }
        
        private void merge(int[] A, int lleft, int lright, int rleft, int rright, int[] temp){
            int i = lleft, j = rleft;
            int index = lleft;
            while (i <= lright && j <= rright) {
                if (A[i] < A[j]) {
                    temp[index++] = A[i++];
                }else  {
                    temp[index++] = A[j++];
                }
            }
            while(i <= lright) {
                temp[index++] = A[i++];
            }
            while(j <= rright) {
                temp[index++] = A[j++];
            }
            for (int k = lleft; k<= rright; k++) {
                A[k] = temp[k];
            }
        }
    }
    

    6 堆排序,O(NlogN),不稳定

    原地排序方法:
    对于一个N个元素的无序数组(下标从0~N-1),首先建立大顶堆

    • 将堆顶元素和最后一个元素交换,即堆顶元素换到了N-1下标
    • 对剩下的0~N-2元素重新生成一个大顶堆
    • 将当前最大值和第N-2下标元素交换
    • 继续反复,知道堆中只剩1个元素,即可停止
    
    

    相关文章

      网友评论

          本文标题:PTA刷题总结-Part3.3 排序专题

          本文链接:https://www.haomeiwen.com/subject/aucpphtx.html