記事ディレクトリ
序文
クイック ソートで最も重要なことは、比較基準としてベンチマーク値キーを選択することです。クイック ソート後、キーは
シーケンスの正しい位置に配置されます。キーを正しい位置に配置すると、他の要素も同様に配置されます。この考え方によれば、複数回再帰してクイック ソートを使用するだけで済みます。
以下は、中央の要素を見つけるために使用するベンチマーク値キーを見つける方法です。
int GetMidNumi(int* a, int left, int right) {
int mid = (right + left) / 2;//
//中间的元素的下标
if (a[left] < a[mid]) {
if (a[mid] < a[right]) {
return mid;
}
else if (a[left] > a[right]) {
return left;
}
else {
return right;
}
}
else {
if (a[mid] > a[right]) {
return mid;
}
else if (a[left] < a[right]) {
return left;
}
else {
return right;
}
}
//最终返回left,right,mid为下标的元素中
//中间值的下标
}
1. 再帰的手法(よく使われる手法)
1.ホーアメソッド:
1 つ覚えておいてください: 左側にキーを作成し、右側で最初に進みます。
挿入ソートは最適化に使用できますが、もちろん使用できません。すべて高速ソート
挿入ソート
を使用します。このリンクには、私のコンテンツが含まれています。挿入ソート
void QuickSort1(int* a, int left, int right) {
if (left >= right) {
return;
//退出此次函数调用
}
if (right - left + 1 > 10) {
//记住一点:左边做key,右边先走
int begin = left;
int end=right;
//保存左右边界值
int midi = GetMidNumi(a, left, right);
//获取中间元素下标
if (midi != left) {
Swap(&a[midi], &a[left]);
}
//把中间位置的数据与最左边进行交换
int keyi = left;
//存储基准值的下标
while (left < right) {
while (left < right && a[right]>=a[keyi]) {
right--;
//右边找小
}
while (left < right && a[left] <= a[keyi]) {
left++;
//左边找大
}
Swap(&a[left], &a[right]);
}
Swap(&a[left], &a[keyi]);
//此时left==right
//left此时的位置就是key的正确位置,让其进行交换
keyi = left;
//以keyi的下标作为分界点,因为其已经在正确位置了
//不需要在进行递归了
//【begin,keyi-1】keyi【keyi+1,end】
QuickSort1(a, begin, keyi - 1);
QuickSort1(a, keyi + 1, end);
}
else {
InsertSort(a + left, (right - left + 1));
//对快排进行优化,数据少的时候就用插入
//当然你也可以if (right - left + 1 > 10)不要
//也不用插入,全进行快排
}
}
2.掘削方法:
void QuickSort2(int* a, int left, int right) {
if (left >= right) {
return;
}
if ((right - left + 1) > 10) {
int begin = left;
int end = right;
int midi = GetMidNumi(a, left, right);
if (midi != left) {
Swap(&a[midi], &a[left]);
}
int key = a[left];
//保存基准的值
int hole = left;
//让最左边为坑
while (left < right) {
while (left < right && a[right] >= key) {
right--;
//右边找小
}
a[hole] = a[right];
//把其放在坑位
hole = right;
//坑位更新,此时右边为坑
while (left < right && a[left] <= key) {
left++;
//左边找大
}
a[hole] = a[left];
hole = left;
//同上
}
a[hole] = key;
//最后hole就是left==right的位置
//也就是key的正确位置
//【begin,hole-1】hole【hole+1,end】
QuickSort1(a, begin, hole - 1);
QuickSort1(a, hole + 1, end);
}
else {
InsertSort(a + left, (right - left + 1));
}
}
3. 前後のポインタ方式:
void QuickSort3(int* a, int left, int right) {
if (left >= right) {
return;
}
if ((right - left) > 10) {
int begin = left;
int end = right;
int midi = GetMidNumi(a, left, right);
if (midi != left) {
Swap(&a[midi], &a[left]);
}
int keyi = left;
int prev = left;
//cur找到比a【keyi】小的值,++prev,cur与prev位置交换
//cur++
//cur找到比a【keyi】大的值,cur++
//也就是无论怎样,cur一定++;
int cur = prev + 1;
while (cur <= right) {
//把比a【keyi】小的值往左翻
//prev要么紧跟着cur
//要么prev与cur之间间隔着一段比a【keyi】
//大的区间
if (a[cur] < a[keyi] && ++prev != cur) {
Swap(&a[cur], &a[prev]);
}
cur++;
}
Swap(&a[prev], &a[keyi]);
//prev为a【keyi】的正确位置
keyi = prev;
//【begin,keyi-1】keyi【keyi+1,end】
QuickSort2(a, begin, keyi - 1);
QuickSort2(a, keyi + 1, end);
}
else {
InsertSort(a + left, (right - left + 1));
}
}
2、非再帰的
非再帰とは、実際には間隔を分割するための迅速なソートのアイデアですが、循環的な方法でのみ行われます。
したがって、インターバル スタックの基本操作を制御するにはスタックを使用する必要があります。
スタックについて理解していない場合は、まずこの記事を読んでください。
int PartSort(int* a, int left, int right) {
int midi = GetMidNumi(a, left, right);
if (midi != left) {
Swap(&a[midi], &a[left]);
}
int prev = left;
int cur = prev + 1;
int keyi = left;
while (cur<= right) {
if (a[cur] < a[keyi] && ++prev != cur) {
Swap(&a[cur], &a[prev]);
}
cur++;
}
Swap(&a[prev], &a[keyi]);
keyi = prev;
//这个其实就是快排的一部分,找到key的正确位置返回
return keyi;
}
void QuickSortNonR(int* a, int left, int right) {
ST st;
STInit(&st);
STPush(&st, right);
//栈先进先出,
//所以【0,9】
//先入右边的【9,0】
//0先出
STPush(&st, left);
while (!STEmpty(&st)) {
int begin = STTop(&st);
STPop(&st);
int end = STTop(&st);
STPop(&st);
int keyi = PartSort(a, begin, end);
if (keyi + 1 < end) {
STPush(&st, end);
STPush(&st, keyi + 1);
}
if (begin < keyi - 1) {
STPush(&st, keyi - 1);
STPush(&st, begin);
}
}
STDestroy(&st);
}