chapter_sorting/quick_sort/ #47
Replies: 74 comments 122 replies
-
js版本错的有点严重啊 |
Beta Was this translation helpful? Give feedback.
-
此部分的尾递归优化能否有图说明呢,看文字简短描述实在是想象不出来 |
Beta Was this translation helpful? Give feedback.
-
为什么里面的循环还需要i<j |
Beta Was this translation helpful? Give feedback.
-
图的文章啥时候出来~等了一个月啦 K神 |
Beta Was this translation helpful? Give feedback.
-
k佬,排序算法这块能不能加上链表的,犹记得第一次面试,被问道链表的逆序,之前从来没考虑过,懵掉了🤣🤣🤣🤣 |
Beta Was this translation helpful? Give feedback.
-
K神,插入排序定义了base基准数,快排里也使用了基准数,但是并没有用局部变量来保存基准数,而在是在哨兵划分中定义了i和j,下边儿这种写法会不会更容易让读者理解呢? int partition(int[] nums, int left, int right) {
// 以 nums[left] 作为基准数,并记录基准数索引
int base = nums[left];
int baseIndex = left;
while (left < right) {
while (left < right && nums[right] >= base)
right--; // 从右向左找首个小于基准数的元素
while (left < right && nums[left] <= base)
left++; // 从左向右找首个大于基准数的元素
swap(nums, left, right); // 交换这两个元素
}
swap(nums, baseIndex, left); // 将基准数交换到两子数组的分界线
return left; // 返回基准数索引
} |
Beta Was this translation helpful? Give feedback.
-
刚发现哨兵划分这里,先 从右往左 还是 从左往右 寻找,结果是不一样的。显然,先 从右往左 是正确的 |
Beta Was this translation helpful? Give feedback.
-
基准数优化,Java代码,' < '本身优先级高于' ^ ',可略去括号 if (nums[left] < nums[mid] ^ nums[left] < nums[right])
return left;
else if (nums[mid] < nums[left] ^ nums[mid] < nums[right])
return mid; |
Beta Was this translation helpful? Give feedback.
-
快排的C语言实现中swap函数是已经自己实现了吗?我并没有查到C语言有封装好的swap函数 |
Beta Was this translation helpful? Give feedback.
-
K神,关于尾递归优化,为什么选短的数组能保证递归深度不超过 |
Beta Was this translation helpful? Give feedback.
-
尾递归优化的那个 未排序的子数组再怎么去处理? |
Beta Was this translation helpful? Give feedback.
-
请问, 如果数据全是一样的, 是不是会退化到n^2, 这该咋办 |
Beta Was this translation helpful? Give feedback.
-
格式出现点问题,例如 (0),都多了“\”,我使用的是edge浏览器 |
Beta Was this translation helpful? Give feedback.
-
这尾递归也牛逼了吧,这是怎么想出来的,我拿着241035自个推了一遍,实在是妙不可言。while (left < right)这个循环太牛了。对内递归,对外跳出递归。 |
Beta Was this translation helpful? Give feedback.
-
为什么在查找元素的时候,需要先要找出比基准数大的值呢?如果先找比基准数小的值的话,排序的结果是不正确的(即交换j--和i++的执行顺序) |
Beta Was this translation helpful? Give feedback.
-
js中一个更容易理解快排思想的代码:
(因为要新开left和right数组,所以更废空间?) |
Beta Was this translation helpful? Give feedback.
-
哨兵划分里面,while循环的条件,怎么判断是该有i<j还是i<=j呢?有时候总是分不清要不要加等号条件 |
Beta Was this translation helpful? Give feedback.
-
d第三步是怎么解释的,看不懂 |
Beta Was this translation helpful? Give feedback.
-
请问 为什么尾递归优化后的快速排序比普通的快速排序多了一层循环? |
Beta Was this translation helpful? Give feedback.
-
作者应该重点说明一下 partition函数中两个while的顺序问题,先后呼唤会报错 |
Beta Was this translation helpful? Give feedback.
-
为什么最后一部分叫做尾递归优化? 这里面left or right =pi+1;才是最后一句,也就是说子函数返回以后还需要原来函数的环境, 不满足尾递归严格定义。并且虽然栈的深度得到优化,但还是有递归深度。不像尾递归递归深度一直是1? 引入迭代,与递归混合,来降低递归深度这一思想倒是能体现。 请高人解答为什么这叫做尾递归优化? |
Beta Was this translation helpful? Give feedback.
-
快排的尾递归优化,感觉rust版本可以改为用saturating_add、saturating_sub实现,这样left和right的类型是usize就可以了: fn quick_sort_tail(nums: &mut [i32], mut left: usize, mut right: usize) {
// 子数组长度为 1 时终止
while left < right {
// 哨兵划分操作
let pivot = partition(nums, left, right);
// 对两个子数组中较短的那个执行快速排序
if pivot - left < right - pivot {
assert!(right > pivot);
quick_sort_tail(nums, left, pivot.saturating_sub(1)); // 递归排序左子数组
left = pivot + 1; // 剩余未排序区间为 [pivot + 1, right]
} else {
assert!(left < pivot);
quick_sort_tail(nums, pivot.saturating_add(1), right); // 递归排序右子数组
right = pivot - 1; // 剩余未排序区间为 [left, pivot - 1]
}
}
} |
Beta Was this translation helpful? Give feedback.
-
#include <iostream>
#include <random>
#include <algorithm>
#include <vector>
// 寻找3数中位数的索引
int calc_median_three(std::vector<int> &vec, int left, int mid, int right) {
int l = vec[left], m = vec[mid], r = vec[right];
if((l < m && m < r) || (r < m && m < l)) return mid;
if((m < l && l < r) || (r < l && l < m)) return left;
else return right;
}
// 寻找基准元素所在位置
int partition(std::vector<int> &vec, int left, int right) {
// 基准元素: vec[left]
// 1. 计算中位数索引
int pivot_index = calc_median_three(vec, left, (right + left) / 2, right);
// 2. 交换基准元素与第一个元素
std::swap(vec[left], vec[pivot_index]);
// 3. 进行划分
int i = left, j = right;
while(i<j){
while(i<j && vec[j] >= vec[left]) j--;
while(i<j && vec[i] <= vec[left]) i++;
std::swap(vec[i], vec[j]);
}
std::swap(vec[left], vec[j]);
return i; // 基准元素所在位置
}
// 快排实现
void qucik_sort(std::vector<int> &vec, int left, int right) {
// 终止递归条件
while(left<right){ // 改成循环->显式控制子数组范围,避免了递归调用的隐式问题,因此结果正确。
// 寻找划分(partition)索引: 中心点(pivot)
int pivot = partition(vec, left, right);
// 尾递归优化: 选择较小的子数组进行递归排序,较大的子数组进行迭代排序
// 这样可以减少递归的深度,避免栈溢出
if ( pivot - left < right - pivot ) {
qucik_sort ( vec , left , pivot - 1 ); // 递归排序左子数组
left = pivot + 1 ; // 剩余未排序区间为 [pivot + 1, right]
} else {
qucik_sort ( vec , pivot + 1 , right ); // 递归排序右子数组
right = pivot - 1 ; // 剩余未排序区间为 [left, pivot - 1]
}
}
}
int main() {
std::vector<int> arr = {1, 2, 3, 4, 5, 6, 7, 8, 9};
std::random_device rd;
std::mt19937 gen(rd());
std::shuffle(arr.begin(), arr.end(), gen);
std::cout << "====================== Before sorting:\t======================\n";
for (const auto& i : arr) std::cout << i << " ";
std::cout << "\n====================== After sorting(upper):\t======================\n";
qucik_sort(arr, 0, arr.size() - 1);
for (const auto& i : arr) std::cout << i << " ";
return 0;
} ====================== Before sorting: ====================== |
Beta Was this translation helpful? Give feedback.
-
day09 |
Beta Was this translation helpful? Give feedback.
-
重要的一点是右哨兵先移动 |
Beta Was this translation helpful? Give feedback.
-
一点点理解,希望对大伙有帮助,如有不对希望指正^_^ //快速排序(quick sort)是一种基于分治策略的排序算法,运行高效,应用广泛。
/*
* 快速排序的核心操作是“哨兵划分”,其目标是:
* 选择数组中的某个元素作为“基准数”,将所有小于基准数的元素移到其左侧,而大于基准数的元素移到其右侧
*
* thinking:从某种程度上来说也是进行“某种选择”,并不将其限定为最大值或是最小值,每一轮结束后也相当于哨兵恰好被移动到自己该去的位置,
* 且顺便将待排数组区分为low/high半区,过程上类似于某种“变异”的二分?
*
* 1、选取数组最左端元素作为基准数,初始化两个指针 i 和 j 分别指向数组的两端
* 2、设置一个循环,在每轮中使用 i(j)分别寻找第一个比基准数大(小)的元素,然后交换这两个元素。
* 3、循环执行步骤 2. ,直到 i 和 j 相遇时停止,最后将基准数交换至两个子数组的分界线。
* */
import java.util.Arrays;
public class Quick {
static void oneQuick(int[] arr, int left, int right) {
//以数组左边第一个元素为哨兵
int sentry = arr[left];
int lowH = left;
int highL = right;
//控制排序区间,不用=是因为
//1、我们想要的最好的退出条件就是lowH==highL
//2、lowH和highH两个指针加起来==全扫描整个数组,没必要越界插手
while(lowH < highL) {
//顺序很重要,必须先从highL指针开始找!!!
//为什么?
//因为我们是以左边第一个元素为哨兵,左边是小于哨兵的区域,所以进行最后一次哨兵归位的交换的时候,
// 我们一定要确保最后的索引位置是恰好小于等于右边第一个元素的
//若我们先从lowH向上找时,当出现这样的场景时就会出错[2]...0 ? 4
//当这个?是一个<= [2]的值时,它就会满足while向上查询的判断条件(lowH < highL && arr[lowH] <= sentry)
//这时执行lowH++; lowH == highL ,两个指针重合,但指向的是一个大于[2]的值
//此时进行交换就会将一个大于哨兵的值换到左边
//从上向下找时其实有同样的问题,但这个算法是以左边第一个元素为哨兵为例,因此必须要先去找第一个大于哨兵的位置
//这样就会引出一个问题,其实这个算法本身谁是哨兵是不重要的,那为什么会存在与哨兵位置相关的这样的bug呢?
//thinking:其实只有最后一次的交换可能会出问题,最后情况可能有两种
//1、[2]... 4 1...
//在这种情况下交换后为[2]... 1 4...
//此时算法中先找的那个指针会先“向前”一步,这样,lowH == highL ,第二个指针不会动。
//此时若要与low区的元素交换【即哨兵本身目前处在low区】则应当先寻highL指针,若要与high区的元素交换则需要先寻lowH
//无解???
//2、[2]...4 ? 1...
//交换后任然取决于哨兵本身的位置情况和?的值的情况。
//所以先尽量以最左或者最右元素为哨兵,最左时先寻highL,最右时先寻lowH
//核心问题在于哨兵本身即为左右半区分界点,它既可以是low区,也可以是high区,这个时候就要看哨兵的初始位置在划分成型时的哪个半区
//high指针做的是找出第一个小于sentry的元素索引
while(lowH < highL && arr[highL] >= sentry)
highL--;
//lowH指针做的是找出第一个大于sentry的元素索引,控制寻找区间
while(lowH < highL && arr[lowH] <= sentry)
lowH++;
//交换
int temp = arr[lowH];
arr[lowH] = arr[highL];
arr[highL] = temp;
//各自“前进”?
// 答:不用,上面的循环很巧妙的可以使指针“前进”。两边交换后“刚好”满足各自的循环里的判定条件,
// 所以可以自己再向前蠕动一步。
}
//哨兵归位
arr[left] = arr[lowH];
arr[lowH] = sentry;
}
public static void main(String[] args) {
int[] arr = Selection.randomArray(20);
System.out.println("Before sort:"+ Arrays.toString(arr));
oneQuick(arr, 0, arr.length-1);
System.out.println("After sort: "+ Arrays.toString(arr));
}
}
/*运输结果
Before sort:[906, 359, 165, 998, 563, 134, 816, 758, 725, 174, 650, 677, 77, 25, 244, 42, 27, 613, 121, 380]
After sort: [121, 359, 165, 380, 563, 134, 816, 758, 725, 174, 650, 677, 77, 25, 244, 42, 27, 613, 906, 998]
进程已结束,退出代码为 0
*/ |
Beta Was this translation helpful? Give feedback.
-
贴一个b站董晓老师的板子 int n, a[100005]; // 全局变量:n是数组长度,a[]存数据
void quicksort(int l, int r) { // [l, r] 是当前要排序的区间
if (l == r) return; // 递归终止:区间只有1个元素,已有序
// 1. 初始化指针、选基准
int i = l - 1, j = r + 1; // 指针初始在区间外,方便后续自增/自减后进入区间
int x = a[(l + r) / 2]; // 选中间位置的数当基准(也可用随机、首/尾元素)
// 2. 分区:把数组分成“≤x”和“≥x”两部分
while (i < j) {
// i 向右找 ≥x 的数:遇到 <x 的就跳过,直到找到 ≥x 的停下
do i++; while (a[i] < x);
// j 向左找 ≤x 的数:遇到 >x 的就跳过,直到找到 ≤x 的停下
do j--; while (a[j] > x);
if (i < j) { // 如果 i、j 没交叉,交换两者位置
swap(a[i], a[j]);
}
}
// 3. 递归处理左右子区间
quicksort(l, j); // 左子区间:[l, j]
quicksort(j + 1, r); // 右子区间:[j+1, r]
} |
Beta Was this translation helpful? Give feedback.
-
贴一个递归版 def quick_sort(arr: list):
if len(arr) <= 1:
return arr
p = arr[0]
less = [i for i in arr[1:] if i <= p]
greater = [i for i in arr[1:] if i > p]
return quick_sort(less) + [p] + quick_sort(greater) |
Beta Was this translation helpful? Give feedback.
-
问一下AI,AI现在很聪明
|
Beta Was this translation helpful? Give feedback.
-
这个代码无法通过leetcode912. 排序数组,浪费时间…… |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
chapter_sorting/quick_sort/
Your first book to learn Data Structure And Algorithm.
https://www.hello-algo.com/chapter_sorting/quick_sort/
Beta Was this translation helpful? Give feedback.
All reactions