排序方法与策略

排序方法种类:

  • 冒泡排序
  • 插入排序
  • 选择排序
  • 归并排序
  • 堆排序
  • 快速排序
  • 基数排序

分类方法:

  • 稳定排序与非稳定排序:两个大小一样的值,在排序后相对位置不变。
  • 内部排序与外部排序:是否需要开辟额外存储空间。
稳定排序:
  1. 冒泡排序 2. 插入排序 3. 归并排序 4. 基数排序
非稳定排序:
  1. 选择排序 2. 堆排序 3. 快速排序

稳定排序:

冒泡排序规则:
  1. 将数组切分为未排序区已排序区
  2. 从头到尾扫描未排序区,若前面元素比后面元素的 交换
  3. 每一轮都将待排序区的最大值放到已排序区的首部(最左侧)。
  4. 直到待排序区没有元素为止。

小优化:当某一轮没有出现两个元素交换则说明排序完成,直接跳出即可

代码:
void bubble_sort(int *num, int n) {
    int flag;
    for (int i = 0; i < n; ++i) {
        flag = 0;
        for (int j = 0; j < n - i; ++j) {
            if (num[j] <= num[j + 1]) continue;
            SWAP(num[j], num[j + 1]);
            flag += 1;
        }
        if (flag == 0) break;
    }
    return;
}
插入排序规则:
  1. 将数组切分为已排序区未排序区
  2. 每一轮都将未排序区首部元素插入到已排序区
  3. 直到待排序区没有元素为止。
代码:
void insert_sort(int *num, int n) {
    for (int i = 1; i < n; ++i) {
        for (int j = i - 1; j >= 0; j--) {
            if (num[j + 1] >= num[j]) break;
            SWAP(num[j], num[j + 1]);
        }
    }
    return ;
}

归并排序:

思考与分析

我们可以借鉴分治思想解决排序问题,我们知道简单插入排序的时间复杂度为 O ( N 2 ) , 如若对于元素组切分为两半,然后对于每一半进行简单插入排序,再通过 O ( N ) 进行合并,那么时间复杂度为多少呢?

( n 2 ) 2 + ( n 2 ) 2 + n = n 2 2 + n   n 2   n 2 2 + n n 2

我们只进行了一次分治,时间复杂度就大幅度降低,那么我们再对这两个数组接续分治将得到一个较优的排序策略。

代码:
void merge_sort(int *num, int l, int r) {
    if (r - l < 16) {
        insert_sort(num + l, r - l + 1);
        return;
    }
    int mid = (l + r) >> 1;
    merge_sort(num, l, mid);
    merge_sort(num, mid + 1, r);
    int *temp =(int*)malloc(sizeof(int) * (r - l + 1));
    int p1 = l, p2 = mid + 1, k = 0;
    while (p1 <= mid || p2 <= r) {
        if (p1 <= mid && (num[p1] <= num[p2] || p2 > r)) {
            temp[k++] = num[p1++];
        } else {
            temp[k++] = num[p2++];
        }
    }
    memcpy(num + l, temp, sizeof(int) * r - l + 1);
    free(temp);
    return;
}

非稳定排序:

选择排序规则:
  1. 将数组切分为已排序区未排序区
  2. 每一轮将未排序区中的最小值放到已排序区的尾部。
  3. 直到未排序区没有元素为止。
代码:
int select_sort(int* nums, int n) {
    for (int i = 0; i < n - 1; ++i) {
        int pos = i, min_value = 0x7fffffff;
        for (int j = i + 1; j < n; ++j) {
            if (min_value > nums[j]) {
                min_value = nums[j];
                pos = j;
            }
        }
        SWAP(nums[pos], nums[i]);
    }
}

快速排序规则:

在快速排序中也采用了分治的思想。
首先我们需要确定一个基值,一般情况下我们选用最左侧的数组,然后使用双指针,一个指向最左侧节点,一个指向最右侧节点。首先右指针向左移动,当发现当前指向的数值小于基值,则与左指针指向的数值进行交换,然后左指针向右移动,当发现当前指向的数值大于基值,则与右指针指向的数值进行交换,然后右指针继续向左移动,直至两指针发生碰撞。碰撞位置指向的值则为基值。我们发现碰撞位置左侧的值都小于基值,碰撞位置右侧的值都大于基值。然后我们再以同样的策略分治解决左侧与右侧。

代码:

void quick_sort(int *nums, int l, int r) {
        int p = l, q = r;
        while (r - l >= 16) {
            int pivot = nums[rand() % (r - l + 1) + l]; // 搅乱原数组 打消最坏情况
            int p = l, q = r;
            do {
                while (nums[p] < pivot) ++p;
                while (nums[q] > pivot) --q;
                if (p < q) {
                    SWAP(nums[p], nums[q]);
                    ++p, --q;
                }
            } while (p <= q);
            quick_sort(nums, l, q);
            l = p;
        }
        ungarded_insert_sort(nums + l, r - l + 1);
}
void ungarded_insert_sort(int* nums , int n) {
    int ind = 0;
    for (int i = 1; i < n; ++i) {
        if (nums[i] < nums[ind]) ind = i;
    }
    SWAP(nums[ind], nums[0]);
    for (int i = 2; i < n; ++i) {
        int j = i;
        while (nums[j] < nums[j - 1]) {  //STL  非监督(减少n^2的边界条件判断)
            SWAP(nums[j], nums[j - 1]);
            j--;
        }
    }
    return;
}

猜你喜欢

转载自blog.csdn.net/Ruger008/article/details/81737299