快速排序回顾

我在做力扣中“数组中的第k个最大元素”问题中,有用到快速排序的思想。现在回顾一下快速排序。

快速排序的主要思想是选择一个基准,把数组中大于该基准的放在基准右边,小于该基准的放在基准左边。

基于力扣题解的快速选择,改造成了快速排序,代码如下:

void quickselect(vector<int>& nums, int l, int r) {
    int i=l,j=r;
    int partition=nums[l];
    while(i<j){
        while(nums[i]<partition)i++;
        while(nums[j]>partition)j--;
        if(i<j)swap(nums[i],nums[j]);
    }
    if(l<r){
        quickselect(nums, l, j-1);
        quickselect(nums, j+1, r);
    }
}

定义两个指针,i移动到大于基准的最左边元素,j移动到小于基准的最右边元素,然后交换;再次移动i和j的指针,知道i==j,进入递归。

另外一种比较好理解的快速排序为:

void quickselect(vector<int>& nums,int l,int r){
    if(l>=r){
        return;//终止条件:子数组长度为0或1时无需排序
    }
    int lt=l;//小于区域的右边界
    int gt=r;//大于区域的左边界
    int i=l+1;//当前元素指针
    int partition=nums[l];//基准元素
    while(i<=gt){
        if(nums[i]<partition){
            swap(nums[i],nums[lt]);//将当前元素交换到小于区域中
            lt++;
            i++;
        }
        if(nums[i]>partition){
            swap(nums[i],nums[gt]);
            gt--;
        }
    }
    //递归处理小于区域,大于区域
    quickselect(nums, l, lt-1);
    quickselect(nums, gt+1, r);
}

即二路快速排序,在函数开始首先判断子数组长度,如果为0或者1时就不用排序直接返回即可。

然后我们定义两个区域:小于区域和大于区域,基准的左边为小于区域,右边为大于区域。

由于数组本身有左右边界,所以我们只用定义小于区域的右边界和大于区域的左边界就可以了。

然后i表示当前遍历元素的指针,从基准的后边一位开始,由于在最开始我们已经排除了数组长度为0或1的情况,所以i=l+1不会超出数组边界。

i小于等于大于区域的左边界进入循环。

在循环中我们进行当前元素与基准元素的判断,如果小于说明当前元素应该在小于区域,这时候需要将当前元素交换到小于区域中,同时小于区域的右边界++,i++

如果大于基准元素,将当前元素交换到大于区域,大于区域的左边界--

我们看一下交换的本质,当前元素大于基准的情况下,先将当前元素与gt位置元素进行交换之后gt-1,因此gt之后的元素都是大于基准元素的。

而当前元素小于基准的情况下,当前元素与lt交换,因此lt位置之前的元素都是小于基准的。

已知lt与i是同时++的,那么当i=gt的时候此时lt与gt之间相差1

所以我们可以知道:

左边的红色圈中的元素都小于基准,右边都大于基准。

并且lt位置的元素是基准,因为i是从l+1开始的,基准的开始为l,那么只有在第一个if中涉及到交换基准,而之后i的位置都会在基准的后一个位置。

所以如果进入第一个if,基准会在gt位置,然后退出循环,满足基准左侧是小于区域,右侧是大于区域。

如果进入第二个if,i与gt在一个位置交换后不改变,然后gt到lt的位置,同样的,满足基准左侧是小于区域,右侧是大于区域。

因此while循环的条件应该是i<=gt,由于gt位置之后才是大于区域所以i不应该超过gt。gt位置本身的元素并没有进行判断,所以i是可以等于gt的。

之后就是在两个区域的递归了。

可是这样的快速排序有一个问题,如果数组中有重复的元素,在判断中没有等于的情况,即指针会发生停滞,从而在while中造成死循环。

那么如果解决呢?

我们可以采用等于划分法,就是在判断中添加等于的判定。

代码如下:

void quickselect(vector<int>& nums, int l, int r) {
    if (l >= r) {
        return;  // 终止条件:子数组长度为0或1时无需排序
    }
    
    int lt = l;  // 小于区域的右边界
    int gt = r;  // 大于区域的左边界
    int i = l + 1;  // 当前元素指针
    int partition = nums[l];  // 基准元素
    
    while (i <= gt) {
        if (nums[i] < partition) {
            swap(nums[i], nums[lt]);  // 将当前元素交换到小于区域
            lt++;
            i++;
        } else if (nums[i] > partition) {
            swap(nums[i], nums[gt]);  // 将当前元素交换到大于区域
            gt--;
        } else {
            i++;  // 当前元素等于基准元素,移动到下一个元素
        }
    }
    
    // 递归处理小于区域和大于区域
    quickselect(nums, l, lt - 1);  // 小于区域
    quickselect(nums, gt + 1, r);  // 大于区域
}

如果出现了相等的元素那么我们就不再进行交换而是直接遍历后面的元素。这样i与lt之间就会拉开距离了。i与lt之间的元素就是重复的元素。

即while循环后会出现这样的情况:

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,这是一个比较复杂的问题,我将尽力回答。首先,让我们回顾一下堆排序和快速排序算法的基本思想。 堆排序算法: 堆排序是一种基于树形结构的排序算法,它的基本思想是将待排序的序列构建成一个大根堆或小根堆,然后将堆顶元素与堆底元素交换,再重新调整堆,直到整个序列有序。 快速排序算法快速排序是一种基于分治思想的排序算法,它的基本思想是选取一个基准元素,将序列分为两个子序列,其中一个子序列中的所有元素都小于基准元素,另一个子序列中的所有元素都大于基准元素,然后对两个子序列分别进行快速排序。 现在,让我们来看一下如何用C语言编程实现这两种排序算法。 堆排序算法的实现: ```c #include <stdio.h> #include <stdlib.h> void swap(int* a, int* b) { int temp = *a; *a = *b; *b = temp; } void heapify(int arr[], int n, int i) { int largest = i; int l = 2 * i + 1; int r = 2 * i + 2; if (l < n && arr[l] > arr[largest]) largest = l; if (r < n && arr[r] > arr[largest]) largest = r; if (largest != i) { swap(&arr[i], &arr[largest]); heapify(arr, n, largest); } } void heapSort(int arr[], int n) { for (int i = n / 2 - 1; i >= 0; i--) heapify(arr, n, i); for (int i = n - 1; i >= 0; i--) { swap(&arr[0], &arr[i]); heapify(arr, i, 0); } } int main() { int n; printf("Enter the number of elements to be sorted: "); scanf("%d", &n); int* arr = (int*)malloc(n * sizeof(int)); printf("Enter the elements to be sorted: "); for (int i = 0; i < n; i++) { scanf("%d", &arr[i]); } heapSort(arr, n); printf("Sorted array: "); for (int i = 0; i < n; i++) { printf("%d ", arr[i]); } free(arr); return 0; } ``` 快速排序算法的实现: ```c #include <stdio.h> #include <stdlib.h> void swap(int* a, int* b) { int temp = *a; *a = *b; *b = temp; } int partition(int arr[], int low, int high) { int pivot = arr[high]; int i = (low - 1); for (int j = low; j <= high - 1; j++) { if (arr[j] < pivot) { i++; swap(&arr[i], &arr[j]); } } swap(&arr[i + 1], &arr[high]); return (i + 1); } void quickSort(int arr[], int low, int high) { if (low < high) { int pi = partition(arr, low, high); quickSort(arr, low, pi - 1); quickSort(arr, pi + 1, high); } } int main() { int n; printf("Enter the number of elements to be sorted: "); scanf("%d", &n); int* arr = (int*)malloc(n * sizeof(int)); printf("Enter the elements to be sorted: "); for (int i = 0; i < n; i++) { scanf("%d", &arr[i]); } quickSort(arr, 0, n - 1); printf("Sorted array: "); for (int i = 0; i < n; i++) { printf("%d ", arr[i]); } free(arr); return 0; } ``` 接下来,让我们看一下如何用二叉链表建立二叉排序树。 二叉排序树的建立: ```c #include <stdio.h> #include <stdlib.h> typedef struct node { int data; struct node* left; struct node* right; } Node; Node* createNode(int data) { Node* temp = (Node*)malloc(sizeof(Node)); temp->data = data; temp->left = NULL; temp->right = NULL; return temp; } Node* insert(Node* root, int data) { if (root == NULL) { return createNode(data); } if (data < root->data) { root->left = insert(root->left, data); } else { root->right = insert(root->right, data); } return root; } void inorderTraversal(Node* root, int arr[], int* index) { if (root == NULL) { return; } inorderTraversal(root->left, arr, index); arr[(*index)++] = root->data; inorderTraversal(root->right, arr, index); } int main() { int n; printf("Enter the number of elements to be sorted: "); scanf("%d", &n); Node* root = NULL; printf("Enter the elements to be sorted: "); for (int i = 0; i < n; i++) { int data; scanf("%d", &data); root = insert(root, data); } int* arr = (int*)malloc(n * sizeof(int)); int index = 0; inorderTraversal(root, arr, &index); // Sorting the array using Heap Sort or Quick Sort // ... printf("Sorted array: "); for (int i = 0; i < n; i++) { printf("%d ", arr[i]); } free(arr); return 0; } ``` 最后,我们将排序后的数组输出,完整的代码如下:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值