【数据结构】七大排序算法

1. 交换顺序

void Swap(int *a, int *b) {
    *a = *a ^ *b;
    *b = *a ^ *b;
    *a = *a ^ *b;
}

2. 排序算法分析

  • 如何判断排序算法是否是稳定的?
    • 如果在排序过程中,没有发生跳跃式的交换,那么该排序算法就是稳定的。

对常见的排序算法进行分类:

  • 稳定的排序
    • 冒泡排序
    • 插入排序
    • 归并排序
  • 不稳定的排序
    • 选择排序
    • 堆排序
    • 快速排序
    • 希尔排序

2.1 快速排序

2.1.1 算法思想

以从小到大为例,选取基准数,把比基准数小的放到左边,反之放到右边,反复执行这一步骤,直到只剩下一个元素为止。

  • 挖坑法:假设取 最右边的数字 作为 基准数,先保存一份基准数的值,然后从左边找比基准数大的数字 begin_index,覆盖基准数所在元素,从右边出发,向左找比基准数小的数字 end_index,覆盖掉之前的 begin_index 的元素,直到 begin_index 与 end_index 相遇,最后把 保存的基准数填到 相遇位置即可。

2.1.2 代码实现

int cutPart(int arr[], int left, int right) {
    int tmp = arr[right];
    int begin = left;
    int end = right;

    while (begin < end) {
        while ((begin < end) && (arr[begin] <= tmp)) {
            begin++;
        }
        arr[end] = arr[begin];
        while ((begin < end) && (arr[end] >= tmp)) {
            end--;
        }
        arr[begin] = arr[end];
    }
    arr[begin] = tmp;

    return begin;
}

void quickRecursion(int arr[], int left, int right) {
    if (left >= right)
        return;

    int mid = cutPart(arr, left, right);
    quickRecursion(arr, left, mid - 1);
    quickRecursion(arr, mid + 1, right);
}

void quickSort(int arr[], int len) {
    quickRecursion(arr, 0, len - 1);
}

2.1.3 算法分析

最坏时间复杂度是在已经有序的情况下发生的。

  • 最坏时间复杂度:O(N^2),平均时间复杂度:O(NlogN)
    • 划分的层数为 O(logN) 或者 O(N)
    • 每一层划分的时间复杂度为 O(N)
  • 空间复杂度:O(logN)
  • 稳定性:不稳定(会发生跳跃式的交换数据)

2.2 归并排序

2.2.1 算法思想

以从小到大为例,将数组中的元素切分,一次分两半,直到只剩下一个数字时,然后开始合并。

2.2.2 代码实现

void merge(int arr[], int *extraArr, int left, int mid, int right) {
    int pos = left;
    int l_pos = left;
    int r_pos = mid + 1;

    while (l_pos <= mid && r_pos <= right)
        extraArr[pos++] = arr[l_pos] <= arr[r_pos] ? arr[l_pos++] : arr[r_pos++];

    while (l_pos <= mid)
        extraArr[pos++] = arr[l_pos++];

    while (r_pos <= right)
        extraArr[pos++] = arr[r_pos++];

    while (left <= right) {
        arr[left] = extraArr[left];
        left++;
    }
}
void mergeRecursion(int arr[], int *extraArr, int left, int right) {
    if (left >= right)
        return;

    int mid = (left & right) + ((left ^ right) >> 1);

    mergeRecursion(arr, extraArr, left, mid);
    mergeRecursion(arr, extraArr, mid + 1, right);

    merge(arr, extraArr, left, mid, right);
}
void mergeSort(int arr[], int len) {
    int *extraArr = (int *)malloc(sizeof(int) * len);
    if (NULL == extraArr) {
        perror("malloc fail");
        exit(EXIT_FAILURE);
    }

    mergeRecursion(arr, extraArr, 0, len - 1);
    free(extraArr);
    extraArr = NULL;
}

2.2.3 算法分析

  • 时间复杂度:O(NlogN)
    • 每一层的归并的时间复杂度为 O(N)
    • 归并的层数为 O(logN)
  • 空间复杂度:O(N)
  • 稳定性:稳定

2.3 插入排序

2.3.1 算法思想

第一个数不需要排序,因此比较趟数 i[1, size - 1] 或者 [1, size)ji - 1 这个位置开始比较(从后往前找),最后比较到下标为 0 的位置或者第一个不大于 array[i] 的数,比 array[i] 大的数都向后移动一格,这样其实就相当于空出来一格,如果找到不大于 array[i] 的数,就把 array[j] 放到该位置的下一个位置。

2.3.2 代码实现

void insertSort(int arr[], int len) {
    /*int i = 1;
    for (; i < len; ++i) {
        int j = i - 1;
        for (; j >= 0 && arr[j] > arr[j + 1]; j--) {
            Swap(&arr[j], &arr[j + 1]);
        }
    }*/

    int i = 1;
    for (; i < len; ++i) {
        int key = arr[i];
        int j = i - 1;
        for (; (j >= 0) && (arr[j] > key); j--) {
            arr[j + 1] = arr[j];
        }
        arr[j + 1] = key;
    }
}

2.3.3 算法分析

  • 时间复杂度:最好(有序) O(N),最坏(逆序)O(N^2)
  • 空间复杂度:O(1)
  • 稳定性:稳定

2.4 冒泡排序

2.4.1 算法思想

以升序为例,比较相邻的两个数字,若前者大于后者,则交换两个数字的顺序,反之则不交换。重复上述操作,直到最大的数字 入最右边,最小的数字 在最左边为止。

假如有 N 个数字排序,那么需要比较 N - 1 趟,在每一趟的比较中,比较次数有所不同,第 i 趟需要比较 N - 1 - i 次。

2.4.2 代码实现

#define TRUE 1
#define FALSE 0

void bubbleSort(int arr[], int len) {
    int i = 0;
    int j = 0;
    int is_swap = TRUE;

    for (; is_swap && (i < len - 1); ++i) {
        j = 0;
        is_swap = FALSE;
        for (; j < len - 1 - i; ++j) {
            if (arr[j] > arr[j + 1]) {
                Swap(arr + j, arr + j + 1);
                is_swap = TRUE;
            }
        }
    }
}

2.4.3 算法分析

  • 时间复杂度:最好(有序) O(N),最坏(逆序)O(N^2)
  • 空间复杂度:O(1)
  • 稳定性:稳定

2.5 选择排序

2.5.1 算法思想

以升序为例,用 min 表示最小值的下标,从第 0 个下标开始,比较第一趟,若发现某个值小于第 0 个下标的值,那么就把 min 置为当前下标,每趟比较结束后,比较 mini 的值,如果发现不一样了,就将这两个下标对应的值交换顺序,执行 size - 1 趟比较后,选择排序完成。

2.5.2 代码实现

void selectSort(int arr[], int len) {
    int i = 0;
    int j = 0;
    int min = 0;

    for (; i < len - 1; ++i) {
        min = i;
        for (j = i + 1; j < len; ++j) {
            min = arr[j] < arr[min] ? j : min;
        }
        if (min != i) {
            Swap(arr + min, arr + i);
        }
    }
}

2.5.3 算法分析

  • 时间复杂度:O(N^2)
  • 空间复杂度:O(1)
  • 稳定性:不稳定

2.6 希尔排序

2.6.1 算法思想

本质上还是插入排序,只是加入了 gap 这一变量,使得数组中的元素 先分组然后插入排序,直到 gap 减为 1,也叫做 缩小增量排序

2.6.2 代码实现

void shellSort(int arr[], int len) {
    int i = 0;
    int j = 0;
    int gap = len;

    while (gap > 1) {
        gap = gap / 3 + 1;
        for (i = gap; i < len; ++i) {
            int key = arr[i];
            for (j = i - gap; j >= 0 && arr[j] > key; j -= gap)
            	arr[j + gap] = arr[j];

            arr[j + gap] = key;
        }
    }
}

2.6.3 算法分析

  • 时间复杂度:最坏O(N^2),平均时间复杂度 O(N^1.3)
  • 空间复杂度:O(1)
  • 稳定性:不稳定

2.7 堆排序

2.7.1 算法思想

先建立大堆(从最后一个有孩子节点的父节点开始向前建立大堆),然后交换最后一个元素和堆顶元素,从堆顶开始向下调整堆,接着让倒数第二个与堆顶元素交换,再向下调整堆,直到只剩下堆顶元素,这样就可以依次得到最大的元素、次大的元素…

2.7.2 代码实现

void AdjustDownRecursion(int arr[], int len, int index) {
    int maxIndex = index;
    int left = index * 2 + 1;
    int right = index * 2 + 2;

    if ((left < len) && (arr[maxIndex] < arr[left]))
        maxIndex = left;
    if ((right < len) && (arr[maxIndex] < arr[right]))
        maxIndex = right;

    if (maxIndex != index) {
        Swap(arr + maxIndex, arr + index);
        AdjustDownRecursion(arr, len, maxIndex);
    }
}

void buildHeap(int arr[], int len) {
    int i = 0;
    // 从后往前 建堆
    for (i = (len - 1 - 1) / 2; i >= 0; --i) {
        AdjustDownRecursion(arr, len, i);
    }
}
void heapSort(int arr[], int len) {
    int i = 0;
    buildHeap(arr, len);
    for (i = len - 1; i > 0; --i) {
        Swap(arr + i, arr);
        // 向下调整
        AdjustDownRecursion(arr, i, 0);
    }
}

2.7.3 算法分析

  • 建堆复杂度为:O(N)
  • 时间复杂度:最坏O(NlogN)
  • 空间复杂度:O(1)
  • 稳定性:不稳定(堆顶元素与最后一个元素交换)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值