【算法与数据结构】排序

一、内部排序(关注时空复杂度、稳定性)

1.插入排序

(1)直接插入排序

算法思想:将待排序的元素插入前面已经排好的序列中。

空间复杂度:O(1) 辅助空间(i, j, temp)为常数级

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

稳定性: 稳定 

// 直接插入排序 n为数组长度 (不带哨兵)
void InsertSort(int A[], int n){
    int i, j, temp;
    for(i = 1; i < n; i++){
        if(A[i] < A[i-1]){
            temp = A[i];
            for(j = i - 1; j >= 0 && temp < A[j]; --j)
                A[j+1] = A[j];
            A[j+1] = temp;
        }
    }
}

 (2)折半插入排序

算法思想:根据待排序的元素,对前面已经排好的序列作折半查找,最后在low所指的位置插入元素,【low, i-1】或 【high, i-1】的元素都往后移。最后在low或high+1处插入元素。

时间复杂度:平均 O(n^{2})

稳定性:稳定(当待排元素和mid所指元素相等时,依旧让low = mid + 1)

// 折半插入排序(带哨兵)
void BinaryInsertSort(int A[], int n){
    int i, j, low, high, mid;
    for(i = 2; i <= n; i++){
        A[0] = A[i];
        low = 1; high = i - 1;
        while(low<=high){
            mid = (low + high)/2;
            if(A[0] >= A[mid]) 
                low = mid + 1;
            else high = mid - 1;
        }
        for(j = i-1; j >= low; --j){
            A[j+1] = A[j];   
        A[low] = A[0];
    }
}   

 2.希尔排序(先部分有序,后全局有序)

算法思想:设置步长,对距离相同步长的元素进行直接插入排序,之后调整步长,直至为1。

空间复杂度:O(1)

时间复杂度:最坏O(n^{2}) ; 当n在某个范围内,时间复杂度可达O(n^{1.3})

稳定性:不稳定

适用性:仅用于顺序表

// 希尔排序(带哨兵)
void ShellSort(int A[], int n){
    int i, j, d;
    for(d = n/2; d >= 1; d = d/2){
        for(i = d+1; i <= n; ++i){
            if(A[i] < A[i-d]) {
                A[0] = A[i];
                for(j = i - d; j > 0 && A[0] < A[j]; j -= d)
                    A[j + d] = A[j];
                A[j + d] = A[0];
            }
        }
    }
}

 3.交换排序

(1)冒泡排序

算法思想:每趟确定一个元素位置

空间复杂度:O(1)

时间复杂度:(有序)最好O(n);对比次数 n - 1;交换次数:0

                      (逆序)最坏O(n^{2});对比(交换)次数 n(n - 1)/2;移动次数:3n(n - 1)/2;

                        平均O(n^{2})

稳定性:稳定

适用性:顺序表,链表均可。

// 冒泡排序
void BubbleSort(int A[], int n){
    int i, j, temp;
    for(i = 0; i < n; i++){
        bool flag = false;    // 若有一趟为发生任何交换,则算法可以提前结束
        for(j = n-1; j > i; j--){
            if(A[j] < A[j-1]){
                temp = A[j];
                A[j] = A[j-1];
                A[j-1] = temp;
                flag = true;
            }
        }
        if(flag == false) return;
    }
}

(2)快速排序

算法思想:将序列不断“划分”,每次“划分”确定一个元素的位置(利用递归)

空间复杂度:O(递归层数)        故最好O({log_{2}}^{n})         最坏O(n)

时间复杂度:O( n * 递归层数)  故最好O(n{log_{2}}^{n})      最坏O(n^{2})    平均O(n{log_{2}}^{n})

稳定性:不稳定

// 快速排序
void QuickSort(int A[], int low, int high){
    if(low < high){
        int pivotpos = Partition(A, low, high);    // 划分
        QuickSort(A, low, pivotpos - 1);           // 划分左子表
        QuickSort(A, pivotpos + 1, high);          // 划分右子表
    }
}

// 每一次“划分”
int Partition(int A[], int low, int high) {
    int pivot = A[low];
    while(low < high) {
        while(low < high && A[high] >= pivot) --high; // 只要比pivot大且low<high,就一直循环
        A[low] = A[high];                             // 把比pivot小的值放在low处
        while(low < high && A[low] <= pivot) ++low; // 只要比pivot小且low<high,就一直循环
        A[high] = A[low];                           // 把比pivot大的值放在high处
    }
    A[low] = pivot;   // low = high时跳出循环,把pivot放在low或high处,位置确定
    return low;       // 返回最后pivot所在位置
}

3.选择排序

(1)简单选择排序

算法思想:每趟选择最小(或最大)的元素插入有序序列。

空间复杂度:O(1)

时间复杂度:O(n^{2})        对比次数:【无论序列初始状态如何都是(n-1趟)】 n(n-1)/2

稳定性:不稳定

适用性:适用顺序表、链表

// 简单选择排序(选择最小加入有序序列)
void SelectSort(int A[], int n){
    int i, j, min;
    for(i = 0; i < n - 1; i++) {
        min = i;
        for(j = i + 1; j < n; j++)
            if(A[j] < A[min])
                min = j;
        if(min != i){
            temp = A[i];
            A[i] = A[min];
            A[min] = temp;
        }
    }
}

(2)堆排序

大根堆:根 >= 左、右 ;小根堆:根 <= 左、右

算法思想:将堆顶元素与待排序列最后一个元素交换位置,之后堆顶元素继续下坠。

时间复杂度:关键字对比字数不超过4n,建堆时间复杂度为O(n); 排序时间复杂度O(n{log_{2}}^{n});

                     所以时间复杂度(取大)为 O(n{log_{2}}^{n});

空间复杂度:O(1)

稳定性:不稳定

【注意】:堆插入元素:新元素放在最后位置,(按堆要求)再将新元素不断上升;

                  堆删除元素:将最后一个元素替换删除元素的位置,(按堆要求)再将该元素不断下坠。

// 建立大根堆(数组0存放哨兵)
void BuildMaxHeap(int A[], int len){
    for(int i=len/2; i>0; i--)    // 从后往前调整非终端结点
        HeadAdjust(A, i, len);
}

// 将以k为根的子树调整为大根堆
void HeadAdjust(int A[], int k, int len){
    A[0] = A[k];
    for(int i = 2*k; i <= len; i*=2){
        if(i < len && A[i] < A[i+1]) i++;
        if(A[0] > A[i]) break;
        else {
            A[k] = A[i];
            k = i;
        }
    }
    A[k] = A[0];
}
// 堆排序完整逻辑
void HeapSort(int A[], int len){
    BuildMaxHeap(A, len);
    for(int i = len; i >1; i--){
        int temp = A[i];
        A[i] = A[1];
        A[1] = temp;
        HeadAdjust(A, 1, i-1);
    }
}

 4.归并排序

(2路归并)算法思想:每趟将相邻两个有序序列合并成有序的一个。

【注意】: m路归并,每次需要对比关键字m-1次。

时间复杂度:每趟O(n);趟数\left \lceil {log_{2}}^{n} \right \rceil;故时间复杂度O(n{log_{2}}^{n});

空间复杂度:O(n)

稳定性:稳定

// 2路归并排序
int *B = (int *)malloc(n * sizeof(int)); //辅助数组B 和 A 大小一样

//A[low...mid]和A[mid+1...high]各自有序,将两个部分合并
void Merge(int A[], int low, int mid, int high){
    int i,j,k;
    for(k = low; k <= high; k++) {
        B[k] = A[k];                //将A中元素全部赋值到B中
    for(i = low, j = mid + 1; i <= mid && j <= high; k++){
        if(B[i] <= B[j]) A[k] = B[i++];
        A[k] = B[j++];
    }
    while(i<=mid) A[k++] = B[i++];
    while(j<=high) A[k++] = B[j++];
}

// 对A进行完整的归并排序
void MergeSort(int A[], int low, int high){
    if(low < high) {
        int mid = (low + high) / 2; //从中间划分
        MergeSort(A, low, mid);
        MergeSort(A, mid+1, high);
        Merge(A, low, mid, high);
    }
}

 5.基数排序

算法思想:初始化:把数据分成d元组,设置基数 r 个空队列;

                  分配:扫描所有关键字位,插入相应r队列;

                  收集:把各个队列中的结点依次出队并链接;

空间复杂度:O(r)

时间复杂度:O(d(n + r))

稳定性:稳定

适用性:1.关键字容易拆分,也d较小   2.每组关键字取值范围不大,r较小   3.数据个数n较大

二、外部排序(关注读写磁盘数)

优化外部排序:增加归并路数 k;--- 败者树 对比次数减少至\left \lceil log_{2}^{k} \right \rceil

                         减少归并段 r。     --- 置换-选择排序

【重要结论】归并过程中的磁盘I/O次数 = 归并树的WPL * 2

严格的k路平衡归并可能需要补充虚段0:

若(n - 1)%(k - 1) = 0 : 不需要补充

若(n - 1)%(k - 1) = u : 补充(k - 1)-  u  个虚段。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值