이 기사의 모든 정렬은 예를 들어 오름차순입니다.
콘텐츠
1. 직접 삽입 정렬
기본 아이디어: 우리가 보통 포커를 할 때 카드 뽑기 단계의 정렬에서 삽입 정렬의 개념을 사용합니다.
1. n번째 요소를 삽입할 때 이전 n-1개의 숫자가 이미 순서대로 있습니다.
2. 이 n번째 숫자를 이용하여 이전의 n-1번 숫자와 비교하여 삽입할 위치를 찾아 삽입합니다. (원래 위치의 숫자는 미리 저장되어 있기 때문에 덮어쓰지 않습니다)
3. 원래 위치의 데이터가 차례로 뒤로 이동합니다.
구현:
①단방향 구현([0,end]의 정렬된 간격에 x를 삽입)
즉, 일반적으로 삽입을 위해 몇몇 숫자를 무작위로 열거하고 삽입할 숫자를 두 가지 경우로 나눕니다.
(1) 삽입할 번호는 앞의 순서번호의 가운데 번호이며 직접비교는 끝+1 위치에 x를 할당한다.
(2) x는 가장 작은 숫자이고 end는 -1의 위치에 도달하고 마지막으로 x를 end+1 위치에 직접 할당합니다.
② 배열 전체 정렬 구현
배열이 처음에 정렬되었는지 몰랐기 때문에 첨자를 제어하고 end는 0부터 시작하고 항상 end+1 위치의 값을 x에 저장하고 루프는 단일 패스로 정렬할 수 있습니다. 끝에서 end= n-2, n-1 위치의 숫자는 x에 저장됩니다.
전체 코드:
void InsertSort(int* a, int n)
{
assert(a);
for (int i = 0; i < n - 1; ++i)
{
int end = i;
int x=a[end+1];//将end后面的值保存到x里面了
//将x插入到[0,end]的有序区间
while (end >= 0)
{
if (a[end] > x)
{
a[end + 1] = a[end]; //往后挪动一位
--end;
}
else
{
break;
}
}
a[end + 1] = x; //x放的位置都是end的后一个位置
}
}
직접 삽입 정렬 요약:
① 요소가 순서에 가까울수록 직접 삽입 정렬의 효율성이 높아집니다.
②시간 복잡도: O(N^2)
최악의 경우 숫자를 삽입할 때마다 이전 숫자를 이동해야 하므로 총 1+2+3+...+n=n(n+1)/2
③ 공간복잡도: O(1)
추가 공간이 없고 일정한 수의 변수만 있음
2. 힐 정렬
기본 아이디어:
1. 먼저 n보다 작은 수를 갭으로 선택하고, 갭과 거리가 있는 모든 숫자를 그룹화하여 사전 정렬(직접 삽입 정렬)
2. 간격보다 작은 다른 숫자를 선택하고 ①의 작업을 반복합니다.
3. gap=1일 때 배열 전체가 그룹인 것과 같으며, 한번 더 삽입하면 전체 순서를 정렬할 수 있다.
예:
구현:
①단일 그룹 소트
기존의 직접삽입과 동일, 즉 원래의 간격이 1이 되고 이제 공백이 되며 각 그룹이 별도로 미리 정렬됩니다.
②여러 그룹이 정렬됩니다.
③ 전체 배열 정렬(컨트롤 갭)
다중 사전 정렬(gap>1) + 하나의 삽입 정렬(gap==1)
(1) 간격이 클수록 사전 정렬이 빠르고 순서에 덜 가깝습니다.
(2) 간격이 작을수록 사전 정렬이 느려지고 순서에 가까워집니다.
결과는 다음과 같습니다.
전체 코드:
void ShellSort(int* a, int n)
{
int gap = n;
while (gap > 1)
{
gap /= 2;
for (int i = 0; i < n - gap; i++)
{
int end = i;
int x = a[end + gap];
while (end >= 0)
{
if (a[end] > x)
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = x;
}
}
}
언덕 정렬 요약:
①힐 정렬은 직접 삽입 정렬의 최적화입니다.
②시간 복잡도: O(N^1.3)
③ 공간복잡도: O(1)
3. 선택 정렬
기본 아이디어:
매번 배열에서 가장 크거나 작은 것을 선택하고 모두 정렬될 때까지 배열의 맨 오른쪽 또는 맨 왼쪽에 저장합니다.
구현:
여기에 최적화를 시켰는데, 한번의 소팅에서 가장 큰 수(a[maxi])와 가장 작은 수(a[mini])를 직접 선택하여 맨 오른쪽과 맨 왼쪽에 배치하여 원래의 2배의 정렬 효율을 낸다.
①단품 주문
가장 작은 수(a[mini])와 가장 큰 수(a[maxi])를 찾아 맨 왼쪽과 맨 오른쪽에 배치합니다.
ps: 시작과 끝은 레코드의 왼쪽과 오른쪽 아래 첨자를 저장하고, 미니 및 맥시 레코드는 최소 및 최대 아래 첨자를 저장합니다.
② 전체 배열 정렬
begin++와 end-- 다음에 남은 n-2개의 숫자를 배열하고 한번의 패스를 다시 수행하여 begin이 end보다 작을 때까지 루프를 형성할 수 있도록 한다.
전체 코드:
void SelectSort(int* a, int n)
{
int begin = 0,end = n - 1;
while (begin<end)
{
int mini = begin, maxi = begin;
for (int i = begin; i <= end; i++)
{
if (a[i] < a[mini])
{
mini = i;
}
if (a[i] > a[maxi])
{
maxi = i;
}
}
Swap(&a[mini], &a[begin]);
//当begin==maxi时,最大值会被换走,修正一下
if (begin==maxi)
{
maxi=mini;
}
Swap(&a[maxi], &a[end]);
begin++;
end--;
}
}
직접 선택 정렬 요약:
① 직접선택정렬은 잘 이해하고 있으나 실제 효율이 높지 않고 거의 사용되지 않음
②시간 복잡도: O(N^2)
③ 공간복잡도: O(1)
넷째, 힙 정렬
기본 아이디어:
1. 큰 힙으로 정렬할 시퀀스를 구성합니다.큰 힙의 특성에 따라 현재 힙의 루트 노드(힙 상단)가 시퀀스에서 가장 큰 요소입니다.
2. 힙의 맨 위 요소를 마지막 요소로 바꾼 다음 나머지 노드를 큰 힙으로 재구성합니다.
3. 큰 힙의 첫 번째 빌드부터 시작하여 2단계를 반복합니다. 빌드할 때마다 시퀀스의 최대값을 얻은 다음 큰 힙의 맨 뒤에 놓을 수 있습니다. 마지막으로 순서가 지정된 시퀀스를 얻습니다.
작은 결론:
오름차순 정렬, 힙 빌드
내림차순으로 정렬하고 작은 힙을 만듭니다.
구현:,
① 알고리즘 하향 조정
주어진 배열 시퀀스를 큰 힙으로 빌드합니다. 루트 노드에서 힙을 빌드하려면 여러 하향 조정 알고리즘이 필요합니다.
힙 하향 조정 알고리즘(전제 사용):
(1) 작은 힙으로 조정하려면 루트 노드의 왼쪽 및 오른쪽 하위 트리가 모두 작은 힙이어야 합니다.
(2) 큰 힙으로 조정하려면 루트 노드의 왼쪽 및 오른쪽 하위 트리가 큰 힙이어야 합니다.
하향 조정 알고리즘의 기본 아이디어:
1. 루트 노드에서 시작하여 왼쪽 및 오른쪽 자식 값이 큰 노드를 선택합니다.
2. 선택한 자녀의 값이 아버지의 값보다 크면 두 값을 교환
3. 큰 아이를 새 아버지로 여기고 잎 노드에 도달할 때까지 계속 하향 조정합니다.
//向下调整算法
//以建大堆为例
void AdJustDown(int* a, int n, int parent)
{
int child = parent * 2 + 1;
//默认左孩子较大
while (child < n)
{
if (child + 1 < n && a[child+1] > a[child ])//如果这里右孩子存在,
//且更大,那么默认较大的孩子就改为右孩子
{
child++;
}
if(a[child]>a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
②힙 빌드(주어진 임의의 배열을 큰 힙으로 빌드)
힙 구축 아이디어:
끝에서 두 번째 non-leaf 노드부터 시작하여 뒤에서 앞으로 차례로 부모로 사용되며 루트 위치로 조정될 때까지 차례로 아래쪽으로 조정됩니다.
힙 다이어그램:
//最后一个叶子结点的父亲为i,从后往前,依次向下调整,直到调到根的位置
for (int i = (n - 1 - 1) / 2;i>=0;--i)
{
AdJustDown(a,n,i);
}
③힙 정렬(힙 삭제 개념을 사용)
힙 정렬의 아이디어:
1. heap이 생성된 후, heap 상단의 숫자를
마지막 숫자로 바꾼다. 2. 마지막 숫자를 보지 말고, 나머지 n-1 숫자를 더미로 조정한 후 1단계로 이동 .3. 끝에 하나의 숫자만 남을 때까지 정지하여 순서대로 배열
for (int end = n - 1; end > 0; --end)
{
Swap(&a[end],&a[0]);
AdJustDown(a,end,0);
}
전체 코드는 다음과 같습니다.
void AdJustDown(int* a, int n, int parent)
{
int child = parent * 2 + 1;
while (child < n)
{
if (child + 1 < n && a[child+1] > a[child ])
{
child++;
}
if(a[child]>a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
//堆排序
void HeapSort(int*a,int n)
{
for (int i = (n - 1 - 1) / 2;i>=0;--i)
{
AdJustDown(a,n,i);
}
for (int end = n - 1; end > 0; --end)
{
Swap(&a[end],&a[0]);
AdJustDown(a,end,0);
}
}
5. 버블 정렬
버블 정렬의 기본 아이디어:
한 번의 여행 과정에서 앞과 뒤의 두 숫자를 차례로 비교하고 더 큰 숫자를 뒤로 밀고 다음에는 나머지 n-1 숫자만 비교하면 되는 식이다.
//优化版本的冒泡排序
void BubbleSort(int* a, int n)
{
int end = n-1;
while (end>0)
{
int exchange = 0;
for (int i = 0; i < end; i++)
{
if (a[i] > a[i + 1])
{
Swap(&a[i], &a[i + 1]);
exchange = 1;
}
}
if (exchange == 0)//单趟过程中,若没有交换过,证明已经有序,没有必要再排序
{
break;
}
end--;
}
}
버블 정렬 요약:
① 매우 이해하기 쉬운 정렬
②시간 복잡도: O(N^2)
③ 공간복잡도: O(1)
6. 빠른 정렬
재귀 버전
1. 호아레 버전
Hoare의 일방적인 생각:
1. 왼쪽에 키를 만들고 오른쪽으로 먼저 이동하여 키보다 작은 값을 찾습니다.
2. 왼쪽으로 돌아가 키보다 큰 값을 찾습니다.
3. 그런 다음 왼쪽과 오른쪽 값을 바꿉니다.
4. 위의 1 2 3 단계를 항상 반복하십시오.
5. 두 사람이 만났을 때의 위치가 가장 왼쪽에 있는 선택된 키 값으로 교환됩니다.
이렇게 하면 키가 올바른 위치에 놓입니다.
애니메이션 데모:
//hoare版本
//单趟排序 让key到正确的位置上 keyi表示key的下标,并不是该位置的值
int partion1(int* a, int left, int right)
{
int keyi = left;//左边作keyi
while (left < right)
{ //右边先走,找小于keyi的值
while (left < right && a[right] >= a[keyi])
{
right--;
}
//左边后走,找大于keyi的值
while (left < right && a[left] <= a[keyi])
{
left++;
}
Swap(&a[left], &a[right]);
}
Swap(&a[left], &a[keyi]);
return left;
}
void QuickSort(int* a, int left, int right)
{
if (left >= right)
return;
int keyi = partion1(a, left, right);
//[left,keyi-1] keyi [keyi+1,right]
QuickSort(a, left, keyi - 1);
QuickSort(a, keyi + 1, right);
}
2. 굴착 방식
사실은 본질적으로 hoare의 변형입니다.
단방향 굴착 방법:
1. 먼저 맨 왼쪽의 첫 번째 데이터를 임시 변수 키에 저장하여 피트를 형성합니다.
2. 오른쪽부터 키보다 작은 값을 찾아 그 값을 구덩이에 던지고 이때 새로운 구덩이를 형성한다.
3. 왼쪽부터 시작하여 키보다 큰 값을 찾아 그 값을 구덩이에 던지고 이때 새로운 구덩이를 형성한다.
4. 1 2 3 단계를 항상 반복하십시오.
5. 양측이 만날 때까지 새로운 구덩이가 형성되고 마침내 그 안에 키 값이 던져진다.
이렇게 하면 키가 올바른 위치에 도달했습니다.
애니메이션 데모:
//挖坑法
int partion2(int* a, int left, int right)
{
int key = a[left];
int pit = left;
while (left < right)
{
while (left < right && a[right] >= key)
{
right--;
}
a[pit] = a[right];//填坑
pit=right;
while (left < right && a[left] <= key)
{
left++;
}
a[pit] = a[left];//填坑
pit=left;
}
a[pit] = key;
return pit;
}
void QuickSort(int* a, int left, int right)
{
if (left >= right)
return;
int keyi = partion2(a, left, right);
//[left,keyi-1] keyi [keyi+1,right]
QuickSort(a, left, keyi - 1);
QuickSort(a, keyi + 1, right);
}
3. 앞 뒤 포인터 방식(이 방법을 추천함)
전면 및 후면 포인터의 아이디어:
1. 처음에 prev를 시퀀스의 시작으로 선택하고 cur 포인터는 prev의 다음 위치를 가리키고 왼쪽의 첫 번째 숫자를 키로 선택합니다.
2. Cur가 먼저 실행되고 키보다 작은 값을 찾은 다음 발견되면 중지합니다.
3、++이전
4. prev와 cur를 첨자 값으로 바꿉니다.
5. 루프에서 2 3 4 단계를 반복합니다.멈춘 후 마지막으로 키와 prev를 아래 첨자 값으로 교환합니다.
이렇게 하면 키도 올바른 위치에 도달합니다.
애니메이션 데모:
int partion3(int* a, int left, int right)
{
int prev = left;
int cur = left + 1;
int keyi = left;
while (cur <= right)
{
if (a[cur] < a[keyi] && ++prev != cur)//prev != cur 防止cur和prev相等时,相当于自己和自己交换,可以省略
{ //前置 ++ 的优先级大于 != 不等于的优先级
Swap(&a[prev], &a[cur]);
}
++cur;
}
Swap(&a[keyi], &a[prev]);
return prev;
}
void QuickSort(int* a, int left, int right)
{
if (left >= right)
return;
int keyi = partion3(a, left, right);
//[left,keyi-1] keyi [keyi+1,right]
QuickSort(a, left, keyi - 1);
QuickSort(a, keyi + 1, right);
}
재귀 확장 그래프
빠른 정렬 최적화
1. 세 숫자의 중국어 방법
퀵정렬은 데이터에 민감하므로 순서가 매우 무질서하고 혼돈되면 퀵정렬의 효율성이 매우 높지만 순서가 순서대로라면 시간복잡도는 O(N*logN)에서 O(N)으로 변한다. ^2), 버블 정렬과 동일
각각의 정렬을 위해 선택한 키가 정확히 시퀀스의 중간 값인 경우, 즉 키가 단일 정렬 후 시퀀스의 중간에 위치하면 빠른 정렬의 시간 복잡도는 O(NlogN)
그러나 이것은 이상적인 상황입니다.우리가 정렬된 배열인 극단적인 경우 시퀀스 집합에 직면했을 때 왼쪽을 키 값으로 선택하면 O(N^2) 복잡성으로 퇴화하므로 이때 첫 번째 위치, 꼬리 위치, 중간 위치에 있는 숫자를 3개의 숫자로 선택하고 중간 위치에 있는 숫자를 선택하여 맨 왼쪽에 놓는다. 최적화 후 모든 것이 이상적인 상황이 됩니다.
//快排的优化
//三数取中法
int GetMidIndex(int* a, int left, int right)
{
int mid = (left + right) / 2;
if (a[left] < a[right])
{
if (a[mid] < a[right])
{
return mid;
}
else if (a[mid] > a[right])
{
return right;
}
else
{
return left;
}
}
else
{
if (a[mid] > a[left])
{
return left;
}
else if (a[mid] < a[right])
{
return right;
}
else
{
return mid;
}
}
}
int partion5(int* a, int left, int right)
{
//三数取中,面对有序时是最坏的情况O(N^2),现在每次选的key都是中间值,变成最好的情况了
int midi = GetMidIndex(a, left, right);
Swap(&a[midi], &a[left]);//这样还是最左边作为key
int prev = left;
int cur = left + 1;
int keyi = left;
while (cur <= right)
{
if (a[cur] < a[keyi] && ++prev != cur)//prev != cur 防止cur和prev相等时,相当于自己和自己交换,可以省略
{ //前置 ++ 的优先级大于 != 不等于的优先级
//++prev;
Swap(&a[prev], &a[cur]);
}
++cur;
}
Swap(&a[keyi], &a[prev]);
return prev;
}
2. 작은 부분구간으로의 재귀
recursion depth가 커질수록 recursion 횟수는 layer당 2배씩 증가하여 효율성에 큰 영향을 미치며, sorting할 sequence의 길이를 일정 크기로 나누었을 때, recursion을 계속하는 효율성 분할은 삽입 정렬보다 나쁩니다. 빠른 행 대신 삽입 행을 사용할 수 있습니다.
분할된 간격의 길이가 10보다 작을 때 삽입 정렬을 사용하여 나머지 숫자를 정렬할 수 있습니다.
//小区间优化法,可以采用直接插入排序
void QuickSort(int* a, int left, int right)
{
if (left >= right)
return;
if (right - left + 1 < 10)
{
InsertSort(a + left, right - left + 1);
}
else
{
int keyi = partion5(a, left, right);
//[left,keyi-1] keyi [keyi+1,right]
QuickSort(a, left, keyi - 1);
QuickSort(a, keyi + 1, right);
}
}
비재귀 버전
재귀 알고리즘은 주로 하위 간격을 나눕니다.비재귀적으로 빠른 정렬을 구현하려면 스택을 사용하여 간격을 저장하기만 하면 됩니다. 일반적으로 재귀 프로그램을 비재귀 프로그램으로 변경할 때 가장 먼저 떠오르는 것은 재귀 자체가 스택을 푸시하는 과정이기 때문에 스택을 사용하는 것입니다.
비재귀의 기본 아이디어:
1. 정렬된 배열의 시작 위치와 끝 위치를 저장할 스택을 신청합니다.
2. 전체 어레이의 시작 및 끝 위치를 스택으로 밀어 넣습니다.
3. 스택의 특성은 후입선출(Last In, First Out)이므로 오른쪽이 스택으로 다시 밀려나기 때문에 오른쪽이 먼저 스택에서 나옵니다.
스택의 맨 위 요소를 받아서 스택을 팝하는 끝을 정의하고, 스택의 맨 위 요소를 받아서 스택을 팝하는 시작을 정의합니다.
4. 배열을 단일 패스로 정렬하고 키 값의 첨자를 반환합니다.
5. 이때 기준값 키의 왼쪽에 순서를 배열해야 합니다.
참조 값 키의 왼쪽에 있는 시퀀스의 시작 위치와 끝 위치만 스택에 저장되어 있으면 왼쪽 정렬 시 다음 간격을 찾지 않습니다. 따라서 먼저 오른쪽 시퀀스의 시작 위치와 끝 위치를 스택에 저장한 다음 왼쪽 시퀀스의 시작 위치와 끝 위치를 스택에 저장합니다.
6. 스택이 비어 있는지 확인하고, 비어 있지 않으면 4, 5단계를 반복합니다. 비어 있으면 정렬이 완료된 것입니다.
void QuickSortNonR(int* a, int left, int right)
{
Stack st;
StackInit(&st);
StackPush(&st,left);
StackPush(&st, right);
while (!StackEmpty(&st))
{
int end = StackTop(&st);
StackPop(&st);
int begin = StackTop(&st);
StackPop(&st);
int keyi = partion5(a,begin,end);
//区间被成两部分了 [begin,keyi-1] keyi [keyi+1,end]
if (keyi + 1 < end)
{
StackPush(&st,keyi+1);
StackPush(&st,end);
}
if (keyi-1>begin)
{
StackPush(&st, begin);
StackPush(&st, keyi -1);
}
}
StackDestroy(&st);
}
빠른 정렬 요약:
① 퀵소트의 전체적인 종합적인 성능과 사용 시나리오는 비교적 양호하므로 감히 퀵소트라고 부른다.
②빠른 정렬의 유일한 막다른 길은 2,3,2,3,2,3,2,3과 같이 정렬되거나 거의 정렬된 시퀀스를 정렬하는 것이며, 이는 O(N^2) 시간 복잡도가 됩니다.
③시간복잡도 O(N*logN)
④ 공간복잡도 O(logN)
일곱, 병합 정렬
병합 정렬의 기본 개념(분할 및 정복 아이디어):
1. ( 분할 ) 배열을 왼쪽 시퀀스와 오른쪽 시퀀스로 나누고 둘 다 따로 정렬한 다음 왼쪽 시퀀스를 왼쪽 시퀀스와 오른쪽 시퀀스로 세분하고 이 단계를 하위 시퀀스에 존재하지 않을 때까지 반복합니다. 간격 또는 숫자가 하나만 있을 때까지
2. ( Merge ) 첫 번째 단계에서 얻은 숫자를 정렬된 간격으로 결합
구현:
① 분할
②병합
재귀적 구현:
이것은 아이디어에서 이진 트리와 매우 유사하므로 재귀 메서드를 사용하여 병합 정렬을 구현할 수 있습니다.
코드 쇼는 아래와 같습니다.
void _MergeSort(int* a, int left, int right, int* tmp)
{
if (left >= right)
{
return;
}
int mid = (left + right) / 2;
_MergeSort(a, left, mid, tmp);
_MergeSort(a, mid+1, right, tmp);
int begin1 = left, end1 = mid;
int begin2 = mid + 1, end2 = right;
int i = left;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[i++] = a[begin1++];
}
else
{
tmp[i++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
for (int j = left; j <= right; j++)
{
a[j] = tmp[j];
}
}
//归并排序
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int)*n);
if (tmp == NULL)
{
printf("malloc fail\n");
exit(-1);
}
_MergeSort(a,0,n-1,tmp);
free(tmp);
tmp = NULL;
}
비재귀적 구현:
재귀적 구현의 단점은 스택이 항상 호출되고 스택 메모리가 종종 매우 작다는 것입니다. 그래서 우리는 루프 방법을 사용하여
배열의 첨자를 조작하기 때문에 위에서 재귀적으로 얻은 배열 첨자를 저장하는 데 도움이 되는 배열을 사용해야 합니다.재귀와의 차이점은 재귀는 항상 간격을 세분화해야 하고 왼쪽 간격은 다음과 같아야 한다는 것입니다. 재귀 적으로 나눕니다. , 다음 재귀 적으로 오른쪽 간격을 나누고 배열의 비 재귀는 데이터를 한 번에 처리하고 매번 첨자를 다시 원래 배열로 복사하는 것입니다.
병합 정렬의 기본 아이디어는 정렬할 시퀀스 a[0...n-1]를 길이가 1인 n개의 정렬된 시퀀스로 간주하고 인접한 정렬된 목록을 쌍으로 병합하여 n/2개의 길이 2를 얻는 것입니다. ;의 정렬된 목록은 이러한 정렬된 시퀀스를 다시 병합하여 길이가 4인 n/4개의 정렬된 시퀀스를 얻고, 마지막으로 길이가 n인 정렬된 시퀀스를 얻습니다.
그러나 우리는 이상적인 상황(짝수)에 있고 특별한 경계 통제가 있습니다.데이터의 수가 짝수가 아닐 때 우리가 나누는 갭 그룹은 경계를 벗어난 위치를 가질 수밖에 없습니다.
첫 번째 경우:
두 번째 경우:
코드 쇼는 아래와 같습니다.
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int)*n);
if (tmp == NULL)
{
printf("malloc fail\n");
exit(-1);
}
int gap = 1;
while (gap < n)
{
for (int i = 0; i < n; i += 2 * gap)
{
// [i,i+gap-1] [i+gap,i+2*gap-1]
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;
// 核心思想:end1、begin2、end2都有可能越界
// end1越界 或者 begin2 越界都不需要归并
if (end1 >= n || begin2 >= n)
{
break;
}
// end2 越界,需要归并,修正end2
if (end2 >= n)
{
end2 = n- 1;
}
int index = i;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[index++] = a[begin1++];
}
else
{
tmp[index++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[index++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[index++] = a[begin2++];
}
// 把归并小区间拷贝回原数组
for (int j = i; j <= end2; ++j)
{
a[j] = tmp[j];
}
}
gap *= 2;
}
free(tmp);
tmp = NULL;
}
병합 정렬 요약:
①단점은 O(N) 공간 복잡도를 필요로 하고, 오프 디스크 정렬 문제를 해결하기 위해 병합 정렬이 더 많다는 것입니다.
②시간 복잡도: O(N*logN)
③ 공간복잡도: O(N)
여덟, 세고 정렬하기
비비교 정렬이라고도 하며 비둘기 구멍 원리라고도 하는 해시 직접 주소 지정 방법의 변형 응용 프로그램입니다.
기본 아이디어:
1. 동일한 요소의 발생 횟수 계산
2. 통계 결과에 따라 데이터를 원래 배열로 다시 복사합니다.
구현:
① 동일한 요소의 발생 횟수를 센다.
주어진 임의의 배열 a에 대해 count 배열 count를 열어야 합니다. a[i]는 몇 개이고 count 배열의 첨자는 몇 개 ++입니다.
여기서 우리는 절대 매핑을 사용합니다. 즉, a[i]의 배열 요소가 몇 개이고 count 배열의 첨자가 몇 ++인 위치에 있지만 데이터 집계의 경우 다음에서 시작하지 않습니다. 1001과 같은 더 작은 숫자, 1002, 1003, 1004와 같은 데이터의 경우 상대 매핑 방법을 사용하여 배열 공간을 여는 낭비를 피할 수 있습니다. count 배열의 공간 크기는 최소값을 빼서 결정할 수 있습니다. 배열의 최대값에서 value + 1(예: range=max-min+1), count 배열 첨자 j =a[i]-min을 얻을 수 있습니다.
② count 배열의 결과에 따라 배열에 데이터를 다시 복사합니다.
count[j]의 데이터 개수는 해당 숫자가 몇 번 등장했는지를 나타내는 것으로 0이면 복사할 필요가 없다.
코드 쇼는 아래와 같습니다.
void CountSort(int* a, int n)
{
int min = a[0], max = a[0];//如果不赋值,min和max就是默认随机值,最好给赋值一个a[0]
for (int i=1;i<n;i++)//修正 找出A数组中的最大值和最小值
{
if (a[i] < min)
{
min=a[i];
}
if (a[i]>max)
{
max=a[i];
}
}
int range = max - min + 1;//控制新开数组的大小,以免空间浪费
int* count = (int*)malloc(sizeof(int) * range);
memset(count,0, sizeof(int) * range);//初始化为全0
if (count==NULL)
{
printf("malloc fail\n");
exit(-1);
}
//1、统计数据个数
for (int i=0;i<n;i++)
{
count[a[i]-min]++;
}
//2、拷贝回A数组
int j = 0;
for (int i=0;i<range;i++)
{
while (count[i]--)
{
a[j++] = i + min;
}
}
free(count);
count = NULL;
}
계산 정렬 요약:
①데이터 범위가 상대적으로 집중되면 효율성이 매우 높지만 사용 시나리오가 매우 제한적이며 음수를 배열할 수 있지만 부동 소수점 수에 대해서는 아무 것도 할 수 없습니다.
②시간 복잡도: O(MAX(N,range))
③ 공간 복잡도: O(범위)
8가지 안정성 요약:
안정정렬은 직접삽입정렬, 버블정렬, 병합정렬
불안정한 정렬에는 힐 정렬, 선택 정렬, 힙 정렬, 빠른 정렬, 개수 정렬이 포함됩니다.