クイックソート
71092 ワード
クイックソートの概要
クイックソート
各グループにピボットを設定し、そのピボットを基準値以上/以下の2つのグループに分けてソートします.
平均時間複雑度の計算
図中の一般例のように、必要なループ呼び出し深さ(分割レベル)lognのため、要素の個数はNであるため、平均時間複雑度はO(Nlogn)である.したがって、非常に高速なソートアルゴリズムとしてよく使用されています.しかし,距離の遠いデータ交換(swap)のため,ソートは不安定である.
クイックソートの利点
(1)最悪の条件以外は,通常非常に速いアルゴリズムである.
クイックソートの欠点
(1)交換したリストを不安定に並べ替える
(2)リストがソートされている場合、ソートに時間がかかる
最悪の場合はです
最悪の場合の時間的複雑さ
例に示すように、配列が整列し、ピボットが最初の要素(最小値)である場合、左側の要素は1つに分割され、右側の要素の個数は毎回n-1つに分割されます.したがって,ループ呼び出し深さ(分割レベル)はn−1を必要とし,要素の個数はnであるため,時間複雑度はO(n^2)である.
整理する
平均時間複雑度:O(nlogn) 時間の複雑度が最も低い:O(n^2) 空間複雑度:O(n) 分割プロセス(partition)
a[pl]>=x plを右に移動し、成立した要素が見つかるまで
prを左に移動して、成立した要素a[pr]<=xが見つかるまで
上記の要素(swap)を同じ位置で停止して交換する
繰り返しスキャンとスワップ後の交点
plとprが交差すると,パケットプロセスが終了し,配列は2つのグループに分けられる.
征服過程.
分割プロセスを繰り返します.
高速Sortアルゴリズムコード plが見られる.これは,要素数が2の場合のみグループ化するためである.校正コール(征服)プロセス prがa[0]の右側(左は、 plがa[n-1]の左側にある場合(right>pl)、右側パケット 非再帰的実装
スタックにプッシュする値は、グループ(配列)の最初の要素と最後の要素のインデックスです.グループが完了すると、左側のグループに対応する範囲インデックスと右側のグループに対応する範囲インデックスにスクロールします.次にスタックでpop範囲の操作を繰り返します.高速ソート非再帰アルゴリズム IntStack.h IntStack.c
上のアルゴリズムでは,配列の個数をスタックの容量に初期化する.ただし、なるべくメモリを使わないほうがいいので、改善してみましょう.上のアルゴリズムは常に左のグループをプッシュし、スタックの一番後ろのプッシュデータは遅くポップアップされます.従って、先に入ったグループは、その後分割される.これにより、プッシュ順に従ってデータの数を制御することができる.要素数の多いグループから をプッシュする.要素数の少ないグループから をプッシュする.
要素の数が多いグループをバッファリングする場合は、要素の数が比較的少ないグループを分割してから、要素の数が多いグループを分割し、スタック容量を削減します.
入力値
要素の多いグループを先にプッシュ
より少ない要素のグループを先にプッシュ
これは、エレメント数の少ないグループで、エレメント数の多いグループが分割および征服されると、スタック内のデータの最大容量が増大するためです.上記の例では、配列サイズは8で、スタックサイズは2のみです.このように容量を減らすと、スタック上の最大容量はlognより小さくなります.popは分割より先であるため,分割過程(深さ)logn回より小さい.スタック容量を減らすには、リフレッシュ前に次のコードを追加します.
高速ソートの改善
左揃え値8としてピボットを選択すると、ピボット値(8)のみのグループと残りのグループに分けられます.1つの要素と残りの要素に分割する分割は一方に偏り、分割プロセスが増加するため、迅速なソートは期待できません.そこで、以下の方法を参考にしてください.
1.高速ソートを改善し、3つの中値を得る
分割する配列に3つ以上の要素がある場合は、中心値の要素の1つを軸として3つの要素を選択します.分割する配列の最初の、中間の、および終了する要素をソートし、中間および終了で2番目の要素を交換します.端点の2番目の要素の値(a[right-1])を選択することにより、分割するターゲット範囲をa[left+1]~a[right-2]に縮小する.
2.挿入ソートを使用したクイックソートの改善
クイックソートは、要素数の少ない配列では処理が速くなく、挿入ソートは配列サイズが小さい場合に効率的です.配列サイズが10個未満の場合は、挿入ソートを使用します.再帰呼び出しの深さを低減します.
左側カーソルplの開始位置左側=左側+1 右カーソルprの開始位置右=右-2 これにより、パケットのサイズが一方に偏ることを回避し、スキャンする要素を3つ減らして、より速い速度でソートすることができます.コード
int,double型,構造体式配列,すべての資料型の配列に適している.ただし、関数名はクイックソートから来ますが、内部では常にクイックソートアルゴリズムが使用されるわけではありません.ヘッダー:#include フォーマット:void qsort(void*base,size tnmemb,size t size,int(*比較)(const void*,const void*) base:ソートする配列 nmemb:要素数 size:貝熱元素サイズ compar:比較関数、comparに保存されている比較関数を使用して をソート
手動で比較関数を作成する必要があります
1.最初の引数の値が小さい場合は-1を返します.
2.最初の引数が2番目の引数と同じ値を指す場合は、0を返します.
3.最初の引数がより大きい値を指す場合は、1を返します.サンプルコード
クイックソート
各グループにピボットを設定し、そのピボットを基準値以上/以下の2つのグループに分けてソートします.
平均時間複雑度の計算
図中の一般例のように、必要なループ呼び出し深さ(分割レベル)lognのため、要素の個数はNであるため、平均時間複雑度はO(Nlogn)である.したがって、非常に高速なソートアルゴリズムとしてよく使用されています.しかし,距離の遠いデータ交換(swap)のため,ソートは不安定である.
クイックソートの利点
(1)最悪の条件以外は,通常非常に速いアルゴリズムである.
クイックソートの欠点
(1)交換したリストを不安定に並べ替える
(2)リストがソートされている場合、ソートに時間がかかる
最悪の場合の時間的複雑さ
例に示すように、配列が整列し、ピボットが最初の要素(最小値)である場合、左側の要素は1つに分割され、右側の要素の個数は毎回n-1つに分割されます.したがって,ループ呼び出し深さ(分割レベル)はn−1を必要とし,要素の個数はnであるため,時間複雑度はO(n^2)である.
整理する
a[pl]>=x plを右に移動し、成立した要素が見つかるまで
prを左に移動して、成立した要素a[pr]<=xが見つかるまで
上記の要素(swap)を同じ位置で停止して交換する
繰り返しスキャンとスワップ後の交点
plとprが交差すると,パケットプロセスが終了し,配列は2つのグループに分けられる.
分割プロセスを繰り返します.
#include <stdio.h>
#include <stdlib.h>
#define swap(type, x, y) do { type t = x; x = y; y = t;} while(0)
void quick_sort(int a[], int left, int right) {
int pl = left; // left pivot
int pr = right; // right pivot
int x = a[(pl + pr) / 2]; // 중앙 요소 값
/* 분할(divide) */
do {
// 왼쪽피봇값과 오른쪽 피봇값을 서로 교환해야함(작은 값은 왼쪽으로 큰 값은 오른쪽으로)
while (a[pl] < x) pl++; // 왼쪽 피봇값이 중앙 요소값보다 작을때
while (a[pr] > x) pr--; // 오른쪽 피봇값이 중앙 요소값보다 클때
if (pl <= pr) { // pl이 pr보다 작을경우나 같을 경우 둘다 이동시켜야함(같을 경우도 값 교환됨)
swap(int, a[pl], a[pr]); // 교환
pl++;
pr--;
}
} while (pl <= pr); // pl과 pr 교차시 종료(두 그룹으로 분할 마침)
/* 정복(conquer) */
if (left < pr) quick(a, left, pr); // pr이 right 값이 됨
if (right > pl) quick(a, pl, right); // pl이 left 값이 됨
}
要素の数が1のグループはグループ化する必要はありません.従って、アルゴリズムの再帰呼び出し部分からleftスタックにプッシュする値は、グループ(配列)の最初の要素と最後の要素のインデックスです.グループが完了すると、左側のグループに対応する範囲インデックスと右側のグループに対応する範囲インデックスにスクロールします.次にスタックでpop範囲の操作を繰り返します.
/*--- 퀵 정렬 (비재귀 버전) ---*/
/* 분할 범위(인덱스 양끝)를 Push, Pop */
void quick_sort(int a[], int left, int right)
{
// 그룹의 첫 요소, 끝 요소 인덱스
IntStack lstack, rstack;
// 스택 초기화
Initialize(&lstack, right - left + 1);
Initialize(&rstack, right - left + 1);
Push(&lstack, left); Print(&lstack); // 맨 앞 인덱스
Push(&rstack, right); Print(&rstack); // 맨 뒤 인덱스
while (!IsEmpty(&lstack)) { // 두 그룹으로 분할 마치면 종료(함수 호출 완료하면 Pop 되므로)
// Pop하면 분할 시작
int pl = (Pop(&lstack, &left), left); // Pop후 왼쪽 스택에 left 대입
int pr = (Pop(&rstack, &right), right); // Pop후 오른쪽 스택에 rigjht 대입
int x = a[(left + right) / 2]; // 중앙값
/* 분할(partition) */
do {
while (a[pl] < x) pl++;
while (a[pr] > x) pr--;
if (pl <= pr) {
swap(int, a[pl], a[pr]);
pl++;
pr--;
}
} while (pl <= pr); // pl과 pr 교차시 종료(두 그룹으로 분할 마침)
/* 스택의 최대 용량 줄이는 코드*/
if (pr - left < right - pl) { // 오른쪽 요소의 개수가 더 많으면
// 오른쪽 그룹 Push 먼저(요소가 작은 왼쪽 그룹 Pop 먼저도서 최대 스택 용량 줄어듬)
swap(int, pl, left); // left는 pl, pl는 left
swap(int, pr, right); // right는 pr, pr는 right
}
/* 그룹 분할 완료후 Push */
if (left < pr) { // 왼쪽 그룹 푸쉬
Push(&lstack, left); // 맨 첫 요소
Push(&rstack, pr); // 맨 끝 요소
}
if (right > pl) { // 오른쪽 그룹 푸쉬
Push(&lstack, pl); // 맨 첫 요소
Push(&rstack, right); // 맨 끝 요소
}
}
Terminate(&lstack);
Terminate(&rstack);
}
/* int형 스택 IntStack(헤더) */
#ifndef ___IntStack
#define ___IntStack
/*--- 스택을 구현하는 구조체 ---*/
typedef struct {
int max; /* 스택 용량 */
int ptr; /* 스택 포인터 */
int* stk; /* 스택의 첫 요소에 대한 포인터 */
} IntStack;
/*--- 스택 초기화 ---*/
int Initialize(IntStack* s, int max);
/*--- 스택에 데이터를 푸시---*/
int Push(IntStack* s, int x);
/*--- 스택에서 데이터를 팝 ---*/
int Pop(IntStack* s, int* x);
/*--- 스택에서 데이터를 피크 ---*/
int Peek(const IntStack* s, int* x);
/*--- 스택 비우기 ---*/
void Clear(IntStack* s);
/*--- 스택의 최대 용량 ---*/
int Capacity(const IntStack* s);
/*--- 스택의 데이터 개수 ---*/
int Size(const IntStack* s);
/*--- 스택이 비어 있나요? ---*/
int IsEmpty(const IntStack* s);
/*--- 스택이 가득 찼나요? ---*/
int IsFull(const IntStack* s);
/*--- 스택에서 검색 ---*/
int Search(const IntStack* s, int x);
/*--- 모든 데이터 출력 ---*/
void Print(const IntStack* s);
/*--- 스택 종료 ---*/
void Terminate(IntStack* s);
#endif
/* int형 스택 IntStack (소스) */
#include <stdio.h>
#include <stdlib.h>
#include "IntStack.h"
/*--- 스택 초기화 ---*/
int Initialize(IntStack* s, int max)
{
s->ptr = 0;
if ((s->stk = calloc(max, sizeof(int))) == NULL) {
s->max = 0; /* 배열의 생성에 실패 */
return -1;
}
s->max = max;
return 0;
}
/*--- 스택에 데이터를 푸시---*/
int Push(IntStack* s, int x)
{
if (s->ptr >= s->max) /* 스택이 가득 참 */
return -1;
s->stk[s->ptr++] = x;
return 0;
}
/*--- 스택에서 데이터를 팝 ---*/
int Pop(IntStack* s, int* x)
{
if (s->ptr <= 0) /* 스택이 비어 있음 */
return -1;
*x = s->stk[--s->ptr];
return 0;
}
/*--- 스택에서 데이터를 피크 ---*/
int Peek(const IntStack* s, int* x)
{
if (s->ptr <= 0) /* 스택이 비어 있음 */
return -1;
*x = s->stk[s->ptr - 1];
return 0;
}
/*--- 스택 비우기 ---*/
void Clear(IntStack* s)
{
s->ptr = 0;
}
/*--- 스택 용량 ---*/
int Capacity(const IntStack* s)
{
return s->max;
}
/*--- 스택에 쌓여 있는 데이터 수 ---*/
int Size(const IntStack* s)
{
return s->ptr;
}
/*--- 스택이 비어 있는가? ---*/
int IsEmpty(const IntStack* s)
{
return s->ptr <= 0;
}
/*--- 스택은 가득 찼는가? ---*/
int IsFull(const IntStack* s)
{
return s->ptr >= s->max;
}
/*--- 스택에서 검색 ---*/
int Search(const IntStack* s, int x)
{
int i;
for (i = s->ptr - 1; i >= 0; i--) /* 꼭대기(top) → 바닥(bottom)으로 선형 검색 */
if (s->stk[i] == x)
return i; /* 검색 성공 */
return -1; /* 검색 실패 */
}
/*--- 모든 데이터 표시 ---*/
void Print(const IntStack* s)
{
int i;
for (i = 0; i < s->ptr; i++) /* 바닥(bottom) → 꼭대기(top)로 스캔 */
printf("%d ", s->stk[i]);
putchar('\n');
}
/*--- 스택 종료 ---*/
void Terminate(IntStack* s)
{
if (s->stk != NULL)
free(s->stk); /* 배열을 삭제 */
s->max = s->ptr = 0;
}
スタック容量の削減上のアルゴリズムでは,配列の個数をスタックの容量に初期化する.ただし、なるべくメモリを使わないほうがいいので、改善してみましょう.上のアルゴリズムは常に左のグループをプッシュし、スタックの一番後ろのプッシュデータは遅くポップアップされます.従って、先に入ったグループは、その後分割される.これにより、プッシュ順に従ってデータの数を制御することができる.
要素の数が多いグループをバッファリングする場合は、要素の数が比較的少ないグループを分割してから、要素の数が多いグループを分割し、スタック容量を削減します.
入力値
要素の多いグループを先にプッシュ
/* 추가한 코드 */
if (pr - left < right - pl) { // 오른쪽 요소의 개수가 더 많으면
// 오른쪽 그룹 Push 먼저(요소가 작은 왼쪽 그룹 Pop 먼저도서 최대 스택 용량 줄어듬)
swap(int, pl, left); // left는 pl, pl는 left
swap(int, pr, right); // right는 pr, pr는 right
}
/* 그룹 분할 완료후 Push */
if (left < pr) { // 왼쪽 그룹 푸쉬
Push(&lstack, left); // 맨 첫 요소
Push(&rstack, pr); // 맨 끝 요소
}
if (right > pl) { // 오른쪽 그룹 푸쉬
Push(&lstack, pl); // 맨 첫 요소
Push(&rstack, right); // 맨 끝 요소
}
追加されたコードは、右側の要素が左側の要素より多い場合、plとleft、pr、rigtのコードを交換します.そうすると、要素の多い部分から、まずはPushです.高速ソートの改善
左揃え値8としてピボットを選択すると、ピボット値(8)のみのグループと残りのグループに分けられます.1つの要素と残りの要素に分割する分割は一方に偏り、分割プロセスが増加するため、迅速なソートは期待できません.そこで、以下の方法を参考にしてください.
1.高速ソートを改善し、3つの中値を得る
分割する配列に3つ以上の要素がある場合は、中心値の要素の1つを軸として3つの要素を選択します.分割する配列の最初の、中間の、および終了する要素をソートし、中間および終了で2番目の要素を交換します.端点の2番目の要素の値(a[right-1])を選択することにより、分割するターゲット範囲をa[left+1]~a[right-2]に縮小する.
2.挿入ソートを使用したクイックソートの改善
クイックソートは、要素数の少ない配列では処理が速くなく、挿入ソートは配列サイズが小さい場合に効率的です.配列サイズが10個未満の場合は、挿入ソートを使用します.再帰呼び出しの深さを低減します.
int sort3elem(int x[], int a, int b, int c) {
if (x[b] < x[a]) swap(int, x[b], x[a]);
if (x[c] < x[b]) swap(int, x[c], x[b]);
if (x[b] < x[a]) swap(int, x[b], x[a]);
return b;
}
void insertion(int a[], int n) {
int i, j;
for (i = 1; i < n; i++) { // 전 값과 비교하기 때문에 1인덱스부터 시작
int tmp = a[i]; // 삽입할 값 저장
for (j = i; j > 0 && a[j - 1] > tmp; j--) // 삽입할 값의 앞만 검사
a[j] = a[j - 1]; // 뒤로 한칸씩 옮김
// memmove(&a[j], &a[j - 1], sizeof(int)); // memory move 위치 이동 함수
a[j] = tmp; // 알맞은 위치에 삽입
}
}
void quick(int a[], int left, int right) { // 배열 분할 함수
int pl = left; // left pivot
int pr = right; // right pivot
int x; // 피봇 변수
int m = sort3elem(a, pl, (pl + pr) / 2, pr); // 한쪽으로 치우쳐 지는 분할 해결
x = a[m]; // 중앙요소 피봇값 대입
swap(int, a[m], a[right - 1]); // 중앙요소 값과 끝에서 두번째 값과 위치 바꿈
/* 스캔할 값 3개 줄임 */
pl++; // left = left + 1
pr -= 2; // right = right - 2
if (right - left < 9)
insertion(&a[left], right - left + 1); // 처음배열 left부터
else {
/* 분할(divide) */
do {
// 왼쪽피봇값과 오른쪽 피봇값을 서로 교환해야함(작은 값은 왼쪽으로 큰 값은 오른쪽으로)
while (a[pl] < x) pl++; // 왼쪽 피봇값이 중앙 요소값보다 작을때
while (a[pr] > x) pr--; // 오른쪽 피봇값이 중앙 요소값보다 클때
if (pl <= pr) { // pl이 pr보다 작을경우나 같을 경우 둘다 이동시켜야함(같을 경우도 값 교환됨)
swap(int, a[pl], a[pr]); // 교환
pl++;
pr--;
}
} while (pl <= pr);
/* 작은 요소개수 그룹부터 분할준비 */
if (pr - left < right - pl) { // 오른쪽 요소의 개수가 더 많으면
// 오른쪽 그룹 함수 호출 먼저
swap(int, pl, left); // left는 pl, pl는 left
swap(int, pr, right); // right는 pr, pr는 right
}
/* 정복 */
if (left < pr) quick(a, left, pr); // pr이 right 값이 됨
if (right > pl) quick(a, pl, right); // pl이 left 값이 됨
}
}
標準ライブラリqsort関数int,double型,構造体式配列,すべての資料型の配列に適している.ただし、関数名はクイックソートから来ますが、内部では常にクイックソートアルゴリズムが使用されるわけではありません.
手動で比較関数を作成する必要があります
1.最初の引数の値が小さい場合は-1を返します.
2.最初の引数が2番目の引数と同じ値を指す場合は、0を返します.
3.最初の引数がより大きい値を指す場合は、1を返します.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char name[10]; /* 이름 */
int height; /* 키 */
int weight; /* 몸무게 */
} Person;
/*--- int형 비교 함수(오름차순 정렬에 사용) ---*/
int int_cmp(const int* a, const int* b)
{
return *a < *b ? -1 : *a > * b ? 1 : 0;
}
/*--- Person형 비교 함수(이름 오름차순 정렬) ---*/
int npcmp(const Person* x, const Person* y)
{
return strcmp(x->name, y->name);
}
/*--- Person형 비교 함수(키 오름차순 정렬) ---*/
int hpcmp(const Person* x, const Person* y)
{
return x->height < y->height ? -1 : x->height > y->height ? 1 : 0;
}
/*--- Person형 비교 함수(몸무게 내림차순 정렬) ---*/
int wpcmp(const Person* x, const Person* y)
{
return x->weight < y->weight ? 1 : x->weight > y->weight ? -1 : 0;
}
/*--- 사람 no명의 데이터를 표시 ---*/
void print_person(const Person x[], int no)
{
int i;
for (i = 0; i < no; i++)
printf("%-10s %dcm %dkg\n", x[i].name, x[i].height, x[i].weight);
}
int main()
{
Person x[] = {
{ "sunmi", 170, 52 },
{ "yoobin", 180, 70 },
{ "sohee", 172, 63 },
{ "jina", 165, 50 },
};
int nx = sizeof(x) / sizeof(x[0]); /* 배열 x의 요소 개수 */
puts("정렬 전");
print_person(x, nx);
/* 이름 오름차순으로 정렬 */
qsort(x, nx, sizeof(Person), (int(*)(const void*, const void*)) npcmp);
puts("\n이름 오름차순으로 정렬 후");
print_person(x, nx);
/* 키 오름차순으로 정렬 */
qsort(x, nx, sizeof(Person), (int(*)(const void*, const void*)) hpcmp);
puts("\n키 오름차순으로 정렬 후");
print_person(x, nx);
/* 몸무게 내림차순으로 정렬 */
qsort(x, nx, sizeof(Person), (int(*)(const void*, const void*)) wpcmp);
puts("\n몸무게 내림차순으로 정렬 후");
print_person(x, nx);
return 0;
}
Reference
この問題について(クイックソート), 我々は、より多くの情報をここで見つけました https://velog.io/@jimmy48/Quick-Sort퀵-정렬テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol