• 数据结构——排序


    排序算法的评价指标

    • 时间复杂度

    • 空间复杂度

    • 稳定性:排序表中相同的两个元素经过该排序算法时无论怎样,排序后这两个元素的相对位置始终没有变化,则称这个排序算法是稳定的;否则为不稳定的。

      (稳定算法不一定比不稳定的算法好)

    排序算法的分类

    • 内部排序:数据都在内存中。(注重更低的时间、空间复杂度)
    • 外部排序:数据量太大,无法全部放入内存中。(重更低的时间、空间复杂度的同时,还要注重磁盘的读写次数)

    内部排序

    插入排序

    直接插入排序

    • 时间复杂度:

      • 最坏(O(n^2))
      • 最好(O(n))
      • 平均(O(n^2))
    • 空间复杂度:(O(1))

    • 稳定

    算法思想

    每次将扫描到的元素插入到前面已经排好序的子序列中

    代码样例
    //由小到大排序
    void Insert_Sort(int num[],int n){
        for(int i=1 ;i < n; i++){
            if(num[i] < num[i-1]){
                int temp = num[i];
                int j;
                for(j = i-1; j >= 0; j--){
                    if(temp < num[j]){
                        num[j+1] = num[j];
                    }
                    else{
                        break;
                    }
                }
                num[j+1] = temp;
            }
        }
    }
    

    折半插入排序

    • 时间复杂度:
    • 最坏(O(n^2))
    • 最好(O(n))
    • 平均(O(n^2))
    算法思想

    直接插入排序的优化,利用折半查找找出需要插入元素的位置。同时为保证算法稳定性,当mid值等于要插入元素的值时应继续执行折半查找的算法(一从小到大为例使(low=mid+1)直到(low>high)为止,之后将其插入到low位置即可。

    代码样例
    //由小到大排序
    void Half_Insert_Sort(int num[],int n){
        for(int i=1 ;i < n; i++){
            if(num[i] < num[i-1]){
                int temp = num[i];
                int low = 0,high = i-1, mid;
                while(low <= high){
                    mid = (low+high)/2;
                    if(num[mid] >= temp)
                        high = mid - 1;
                    else{
                        low = mid + 1;
                    }
                }
                for(int j = i-1; j >= low; j--){
                    num[j+1] = num[j];
                }
                num[low] = temp;
            }
        }
    }
    

    链表的插入排序

    • 时间复杂度:
      • 最坏(O(n^2))
      • 最好(O(n))
      • 平均(O(n^2))

    减少了查找后移动的步骤

    代码样例
    //由小到大排序
    void LinkList_Insert_Sort(LinkList *L){
        Node *p,*r,*s;
        *p = L->next;
        while(p->next != NULL){
            *r = p->next;
            if(r->data < p->data){
                p->next = r->next;
                *s = L;
                while(s->next->data <= r->data){
                    s = s->next;
                }
                r-next = s->next->data;
                s->next = r;
            }
        }
    }
    

    希尔排序

    • 时间复杂度:无法用数学确切证明

      • 最坏当间距等于1时退化为直接插入排序(O(n^2))
      • n在某一个范围内为(O(n^{1.3}))
    • 空间复杂度:(O(1))

    • 不稳定,仅适用于顺序表

    算法思想

    将排序的列表每次以某间距组成新的子表,在各自的子表内进行直接排序。其中每次间隔不断减小(如间隔为上次间隔的一半)

    代码样例

    //由小到大排序
    void Shell_Sort(int num[],int n){
        for(int d = n/2; d >= 1; d/=2){
            for(int i = d; i < n; i++){//为了便于实现代码,每次对子表两个位置进行插入排序,之后对下一个子表相应位置进行插入排序
            cout << i << endl;
                if(num[i-d] > num[i]){
                    int temp = num[i];
                	int j;
               		for(j = i-d; j >= 0; j-=d){
                    	if(temp < num[j]){
                        	num[j+d] = num[j];
                    	}
                    	else{
                        	break;
                    	}
                	}
                	num[j+d] = temp;
                }
            }
        }
    }
    

    交换排序

    冒泡排序

    • 时间复杂度:

      • 最好(O(n))
      • 最坏(O(n^2))
    • 空间复杂度:(O(1))

    • 稳定

    算法思想

    从到尾或者从尾到头两两相互比较交换位置

    代码样例
    //由小到大排序,大的往后冒
    void Bubble_Sort(int num[],int n){
        for(int i=0; i < n-1; i++){
            for(int j=0; j < n-1; j++){
                if(num[j+1] < num[j]){
                    int temp = num[j];
                    num[j] = num[j+1];
                    num[j+1] = temp;
                }
            }
        }
    }
    

    ★★★快速排序

    • 时间复杂度:(O({n} imes{递归层数}));把每次递归调用依次列出来可以看到是一个二叉树,所以(递归层数=二叉树高度)

      • 最坏(左右划分不均匀)(O(n^2))
      • 最好(左右划分不均匀)(O(nlog_{2}n))
      • 平均(左右划分不均匀)(O(nlog_{2}n))
    • 空间复杂度:(O(递归层数))

      • 最坏(左右划分不均匀)(O(n))
      • 最好(左右划分均匀)(O(log_{2}{n}))
    • 不稳定

    算法思想

    以某一数为基准,将比基数基数大和比基数小的分别放在基数两边。之后对左右两边的子序列递归调用该方法

    代码样例
    void Quick_Sort(int num[], int left, int right){
        if(left >= right)
            return;
        int temp=num[left];
        int i = left;
        int j = right;
        while(i != j){
            while(temp <= num[j] && i < j){
                j--;
            }
            while(temp >= num[i] && i < j){
                i++;
            }
            if(i < j){
                int t = num[i];
                num[i] = num[j];
                num[j] = t;
            }
        }
        //基准数归位
        num[left] = num[i];
        num[i] = temp;
        Quick_Sort(num,left,i-1);//递归调用
        Quick_Sort(num,i+1,right);
    }
    

    选择排序

    简单选择排序

    • 时间复杂度:(O(n^2))

    • 空间复杂度:(O(1))

    • 不稳定

    算法思想

    在待排序序列中每次找到最小(或最大)的元素放在有序的序列中

    代码样例
    void Simple_Select_Sort(int num[], int n){
        for(int i=0; i < n-1; i++){
            int min_pos = i;
            for(int j=i+1; j < n; j++){
                if(num[j] < num[min]){
                    min_pos = j
                }
            }
            if(min != i){
                int temp = num[i];
                nun[i] = num[min_pos];
                num[min_pos] = temp;
            }
        }
    }
    

    ★★★堆排序

    • 时间复杂度:(O(nlog_{2}{n}))

    • 空间复杂度:(O(1))

    • 不稳定

    算法思想

    同简单选择排序,但其通过堆来实现。

    细分为大根堆小根堆,在逻辑上是完全二叉树的顺序存储。其中大根堆即({根节点}ge{左、右子节点});小根堆即({根节点}le{左、右子节点})

    堆排序Heap_Sort具体分为三部:

    • 第一步:建立初始堆Build_Heap

    • 第二步:交换根节点与最后元素的位置Swap

    • 第三步:维护堆Heapify

    大根堆得到的为递增序列,小根堆得到的是递减序列。

    代码样例
    //从小到大排序,建立大根堆
    
    void Swap(int i,int j){
        int temp = i;
        i = j;
        j = temp;
    }
    
    void Heapify(int num[], int n; int i){
        if(i >= n){
            return ;
        }
        int c1 = 2 * i + 1;
        int c2 = 2 * i + 2;
        int max_pos = i;
        if(num[c1] > num[max] && c1 < n){
            max_pos = c1;
        }
        if(num[c2] > num[max] && c2 < n){
            max_pos = c2;
        }
        if(max != i){
            Swap(num[max],num[i]);
            Heapify(num,n,max);
        }
    }
    
    void Build_Max_Heap(int num[],int n){
        int last_node = n-1;
        int parent = (last_node - 1)/2;
        for(int i=parent; i >= 0; i--){
            Heapify(int num, int n, int i)
        }
    }
    
    void Heap_Sort(int num,int n){
        Build_Max_Heap(num,n);
        for(int i=n-1; i >= 0; i--){
            Swap(num[i],num[0]);
            Heapify(num, i, 0);
        }
    }
    
    堆的插入和删除

    向堆中插入新元素时,应插入到堆底后面;

    删除元素时,需要将堆底最后一项移动到删除的位置上。

    归并排序(二路归并排序)

    • 时间复杂度:归并树是倒立二叉树

      • (O(nlog_{2}{n}))
    • 空间复杂度:主要来自于辅助数组

      • (O(n))
    • 稳定

    算法思想

    把两个或者多个已经有序的序列合成一个有序的序列

    代码样例

    int *temp_num = (int *)malloc(n*sizeof(int));
    void Merge(int num,int left, int mid, int high){
        int i,j,k;
        for(k=low; k <= high; k++){
            temp_num[k] = num[k];
        }
        for(i=low, j=mid+1,k=left; i <= mid && j <= right; k++){
            if(temp_mun[i] <= temp_num[k]){
                num[k] = temp_num[i++];
            }else{
                num[k] = temp_num[j++];
            }
        }
        while(i <= mid){
            num[k++]=temp_num[i++];
        }
        while(j <= right){
            num[k++]=temp_num[j++];
        }
    }
    
    void Merge_Sort(int num[], int left,int right){
        if(left < right){
            int mid = (left+right)/2;
            Merge_Sort(num,left,mid);//左边递归调用
            Merge_Sort(num,mid+1,right);//右边递归调用
            Merge(num,left,mid,right);左右两路进行归并
        }
    }
    

    更多路见下文外部排序中的K路归并排序

    基数排序

    • 时间复杂度:

      • (O(d imes(n+r)))
    • 空间复杂度:

      • (O(r))
    • 稳定

    算法思想

    将待排序元素的关键字拆成d组,之后根据关键字做d躺的“分配”和“收集”

    • 适合用于:
      • 数组元素的关键字可以方便拆分成d组,且d比较小;
      • 待排序元素n比较大;
      • 每组关键字的范围r比较小;

    见下图例子

    img

    外部排序

    K路归并

    算法思想

    败者树

    算法思想

    最佳排序树

    算法思想

  • 相关阅读:
    Spring学习总结(六)——Spring整合MyBatis完整示例
    Spring学习总结(五)——Spring整合MyBatis(Maven+MySQL)二
    Spring学习总结(五)——Spring整合MyBatis(Maven+MySQL)一
    Spring学习总结(四)——表达式语言 Spring Expression Language
    Spring学习总结(三)——Spring实现AOP的多种方式
    Spring学习总结(二)——静态代理、JDK与CGLIB动态代理、AOP+IoC
    Spring集成MyBatis完整示例
    数据库字符集的坑
    MYSQL中的UNION和UNION ALL
    MySQL的事务和锁
  • 原文地址:https://www.cnblogs.com/cafu-chino/p/15362804.html
Copyright © 2020-2023  润新知