1. 삽입정렬
1.1 기본 아이디어
모든 레코드가 삽입될 때까지 정렬할 레코드를 키 값의 크기에 따라 하나씩 정렬된 시퀀스에 삽입하고 새로운 시퀀스를 얻습니다.
우리에게 익숙한 Landlord는 삽입정렬이다.
1.2 코드 구현
여기서는 순서가 없는 배열을 순서가 있는 배열로 바꿉니다.
- 삽입 정렬 시간 복잡도 분석
- 최선의 경우: 정렬할 배열이 순서대로 지정됩니다.
현재 숫자와 다음 숫자를 비교해보세요
총 N-1개의 비교가 필요합니다.
时间复杂度为 : O ( N )
- 최악의 경우: 정렬할 배열이 역순으로 되어 있는 경우
각 이동 후의 숫자가 뒤로 이동될 수 있습니다.
시간 복잡도는 O(N^2)입니다.
그림이 보여주듯이:
여기서는 두 개의 변수 end와 tmp를 정의해야 합니다. 하나는 첫 번째 요소를 가리키고 다른 하나는 다음 요소를 가리킵니다.
end<tmp일 때 이동하지 마세요(그 반대도 마찬가지).
코드 표시:
void InsertSort(int* a, int n)
{
for (int i = 0; i < n - 1; ++i)
{
// [0, end] 有序,插入tmp依旧有序
int end = i;
int tmp = a[i + 1];
while (end >= 0)
{
if (a[end] > tmp)
{
a[end + 1] = a[end];
--end;
}
else
{
break;
}
}
a[end + 1] = tmp;
}
}
2. 선택 정렬
2.1 기본 아이디어
매번 정렬할 데이터 요소 중 가장 작은(또는 가장 큰) 요소를 선택하고, 정렬할 모든 데이터 요소가 소진될 때까지 시퀀스의 시작 부분에 저장합니다.
몇 가지 질문이 테스트됩니다.
과거에는 각 패스에 대해 최대값 또는 최소값을 선택했지만
최적화 후: 한 패스에 대해 두 개를 선택하고 가장 큰 것과 가장 작은 것이
각각 배열의 왼쪽과 오른쪽 끝에 배치됩니다.
2.2 코드 구현
- 선택 정렬 시간 복잡도 분석
배열의 순서 여부에 관계없이
선택 정렬은 배열을 계속해서 순회해야 합니다.
여기서 몇 가지 최적화를 수행하더라도
여전히 2/N 번 진행한 다음 해당 횟수를 곱해야 합니다. N
시간 복잡도는 O(N^2)입니다.
이를 보다 직관적으로 보여주기 위해 그림을 직접 가져와 설명합니다.
void Swap(int* p1, int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void SelectSort(int* a, int n)
{
int begin = 0, end = n - 1;
while (begin < end)
{
int maxi = begin, mini = begin;
for (int i = begin; i <= end; i++)
{
if (a[i] > a[maxi])
{
maxi = i;
}
if (a[i] < a[mini])
{
mini = i;
}
}
Swap(&a[begin], &a[mini]);
// 如果maxi和begin重叠,修正一下即可
if (begin == maxi)
{
maxi = mini;
}
Swap(&a[end], &a[maxi]);
++begin;
--end;
}
}
3. 힐 정렬
3.1 기본 아이디어:
- 직접 삽입 정렬을 최적화합니다.
1. 分组预排序,使数组接近有序
2. 直接插入
时间复杂度的分析:
일반적으로 기본 Hill 정렬의 시간 복잡도는 다음과 같습니다.
O (N * log2 N) 또는
O (N * log3 N)수많은 실험을 통해 얻은 데이터에 따라 최종적으로 복잡도를 다음과 같이 설정했습니다.
시간 복잡도 범위:
n1.3으로 직접 간주할 수도 있습니다.
여기서 그룹의 사전배열과 삽입의 개념이 동일하다는 것을 그림을 보면 알 수 있다
3.2 코드 구현
//希尔排序
void ShellSort(int* a, int n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;
for (int i = 0; i < n - gap; i++)
{
int end = i;
int tmp = a[end + gap];
while (end >= 0)
{
if (tmp < a[end])
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
4. 힙 정렬
4.1 기본 아이디어
建立一个大堆(升序建大堆)
将堆顶元素与最后一个元素交换
交换后堆元素减一,重新调整堆
한 사이클 후에 배열의 최대값을 마지막 위치에 넣고, 다음 사이클에서는 마지막에서 두 번째 값을 마지막에서 두 번째 위치에 넣는 식으로 배열이 정렬될 때까지 계속됩니다.
- 오름차순 정렬 - 큰 더미 만들기
- 내림차순으로 정렬 - 작은 힙 만들기
- 왼쪽 자식: 자식 = 부모 * 2 + 1
- 오른쪽 자식: 자식 = 부모 * 2 + 2.
- 부모 = (자식 - 1) / 2
4.2 코드 구현
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 = 2 * parent + 1;
}
else
{
break;
}
}
}
//堆排序
void HeapSort(int* a, int n)
{
//建堆--升序建大堆
//向下调整建堆 --时间复杂度:O(n)
for (int i = (n - 2) / 2; i >= 0; i--)
{
AdjustDown(a, n, i);
}
int end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
end--;
}
}
5. 빠른 정렬
5.1 기본 아이디어
빠른 정렬 재귀 버전 1 - hoare
주의점:
- 왼쪽의 키를 선택하면 오른쪽으로 먼저 이동하고, 오른쪽의 키를 선택하면 왼쪽으로 먼저 이동합니다.
- 오른쪽에서 a[keyi]보다 작은 값을 선택하고 왼쪽에서 a[keyi]보다 큰 값을 선택합니다.
- 특별한 경우의 경계 처리에 유의하십시오.
5.1.2 코드 구현
int GetMidnum(int* a, int left, int right)
{
int mid = (left + right) / 2;
if (a[left] > a[mid])
{
if (a[mid] > a[right])
{
return mid;
}
else if(a[left] > a[right])
{
return right;
}
else
{
return left;
}
}
//a[left] <= a[mid]
else
{
if (a[mid] < a[right])
{
return mid;
}
else if (a[left] < a[right])
{
return right;
}
else
{
return left;
}
}
}
int PartSort1(int* a, int left, int right)
{
int keyi = left;
int mid = GetMidnum(a, left, right);
Swap(&a[mid], &a[keyi]);
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]);
keyi = left;
return keyi;
}
5.2 퀵 정렬 재귀 버전 2 - 구덩이 파기 방법
5.2.1 기본 아이디어
이것은 이해의 측면에서 호레와 다르며, 구멍을 파는 생각이 더 이해하기 쉽습니다. 어느 지역을 먼저 갈 것인가의 문제이다.
구멍을 파서 집어넣는다는 의미입니다.
5.2.2 코드 구현
int GetMidnum(int* a, int left, int right)
{
int mid = (left + right) / 2;
if (a[left] > a[mid])
{
if (a[mid] > a[right])
{
return mid;
}
else if(a[left] > a[right])
{
return right;
}
else
{
return left;
}
}
//a[left] <= a[mid]
else
{
if (a[mid] < a[right])
{
return mid;
}
else if (a[left] < a[right])
{
return right;
}
else
{
return left;
}
}
}
int PartSort2(int* a, int left, int right)
{
int mid = GetMidnum(a, left, right);
Swap(&a[mid], &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[left] = key;
return left;
}
5.3 앞뒤 포인터 방식
5.3.1 기본 아이디어
- 두 개의 포인터 cur 및 prev 정의
- cur는 두 번째 요소를 가리킨다.
- prev는 cur 앞의 요소를 가리킨다.
c找比基准值小的值
找到后停下,p向后走一格
再交换c和p指向的值
- 마지막으로 c가 배열을 탐색한 후:
交换key和prev的值
5.3.2 코드 구현
int GetMidnum(int* a, int left, int right)
{
int mid = (left + right) / 2;
if (a[left] > a[mid])
{
if (a[mid] > a[right])
{
return mid;
}
else if(a[left] > a[right])
{
return right;
}
else
{
return left;
}
}
//a[left] <= a[mid]
else
{
if (a[mid] < a[right])
{
return mid;
}
else if (a[left] < a[right])
{
return right;
}
else
{
return left;
}
}
}
int PartSort3(int* a, int left, int right)
{
int keyi = left;
int prev = left;
int cur = prev + 1;
int mid = GetMidnum(a, left, right);
Swap(&a[mid], &a[left]);
while (cur <= right)
{
if (a[cur] < a[keyi] && prev++ != cur)
{
Swap(&a[cur], &a[prev]);
}
cur++;
}
Swap(&a[prev], &a[keyi]);
keyi = prev;
return prev;
}
6. 병합 정렬
6.1 기본 아이디어
- 전체적으로 배열을 질서있게 만들기 위해서는 왼쪽 절반과 오른쪽 절반을 순서대로 변경한 후 단일 병합 및 정렬을 수행하여 배열을 A와 B의 두 부분으로 분할해야 합니다. A 배열을 질서있게 만들기 위해서는 다음과 같습니다. 필요한 경우 A도 두 부분으로 분할되어 왼쪽/오른쪽 절반이 정렬되고 배열에 요소가 하나만 있을 때까지 계속 분할됩니다.
6.2 코드 구현
void MergeSortNonR(int* a, int n)
{
int* tem = (int*)malloc(sizeof(int) * n);
int begin = 0;
int end = n - 1;
int gap = 1;
while (gap < n)
{
int j = 0;
for (int i = 0; i < n; i += 2 * gap)
{
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;
//printf("修正前:[ %d , %d ] [ %d , %d ]\n",begin1,end1,begin2,end2);
if (end1 >= n || begin2 >= n)
{
break;
}
if (end2 >= n)
{
end2 = n - 1;
}
//printf("修正后:[ %d , %d ] [ %d , %d ]\n", begin1, end1, begin2, end2);
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tem[j++] = a[begin1++];
}
else
{
tem[j++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tem[j++] = a[begin1++];
}
while (begin2 <= end2)
{
tem[j++] = a[begin2++];
}
memmove(a + i, tem + i, sizeof(int) * (end2 - i + 1));
}
gap *= 2;
}
}
병합 정렬 비재귀 버전 2(전체 병합 아이디어 구현)
//归并排序 -- 非递归版2
void MergeSortNonR2(int* a, int n)
{
int* tem = (int*)malloc(sizeof(int) * n);
int begin = 0;
int end = n - 1;
int gap = 1;
while (gap < n)
{
int j = 0;
for (int i = 0; i < n; i += 2 * gap)
{
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;
//printf("修正前:[ %d , %d ] [ %d , %d ]\n",begin1,end1,begin2,end2);
if (end1 >= n)
{
end1 = n - 1;
begin2 = n;
end2 = n - 1;
}
else if(begin2 >= n)
{
begin2 = n;
end2 = n - 1;
}
else if(end2 >= n)
{
end2 = n - 1;
}
//printf("修正后:[ %d , %d ] [ %d , %d ]\n", begin1, end1, begin2, end2);
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tem[j++] = a[begin1++];
}
else
{
tem[j++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tem[j++] = a[begin1++];
}
while (begin2 <= end2)
{
tem[j++] = a[begin2++];
}
}
memmove(a, tem, sizeof(int) * n);
gap *= 2;
}
}
이상이 오늘 이야기한 6대 주요 순위입니다. 많은 관심과 사랑 부탁드립니다. 다음에 만나요