데이터 구조 - 6가지 정렬(삽입, 선택, Hill, 버블, 힙, 퀵 정렬)


1. 삽입정렬

1.1 기본 아이디어

모든 레코드가 삽입될 때까지 정렬할 레코드를 키 값의 크기에 따라 하나씩 정렬된 시퀀스에 삽입하고 새로운 시퀀스를 얻습니다. 

우리에게 익숙한 Landlord는 삽입정렬이다.

 1.2 코드 구현

여기서는 순서가 없는 배열을 순서가 있는 배열로 바꿉니다.

  1. 삽입 정렬 시간 복잡도 분석
  • 최선의 경우: 정렬할 배열이 순서대로 지정됩니다.

현재 숫자와 다음 숫자를 비교해보세요

총 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 코드 구현

  1. 선택 정렬 시간 복잡도 분석

배열의 순서 여부에 관계없이
선택 정렬은 배열을 계속해서 순회해야 합니다.
여기서 몇 가지 최적화를 수행하더라도
여전히 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 기본 아이디어

  1. 建立一个大堆(升序建大堆)
  2. 将堆顶元素与最后一个元素交换
  3. 交换后堆元素减一,重新调整堆

한 사이클 후에 배열의 최대값을 마지막 위치에 넣고, 다음 사이클에서는 마지막에서 두 번째 값을 마지막에서 두 번째 위치에 넣는 식으로 배열이 정렬될 때까지 계속됩니다.

  1. 오름차순 정렬 - 큰 더미 만들기
  2. 내림차순으로 정렬 - 작은 힙 만들기
  3. 왼쪽 자식: 자식 = 부모 * 2 + 1
  4. 오른쪽 자식: 자식 = 부모 * 2 + 2.
  5. 부모 = (자식 - 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

주의점:

  1. 왼쪽의 키를 선택하면 오른쪽으로 먼저 이동하고, 오른쪽의 키를 선택하면 왼쪽으로 먼저 이동합니다.
  2. 오른쪽에서 a[keyi]보다 작은 값을 선택하고 왼쪽에서 a[keyi]보다 큰 값을 선택합니다.
  3. 특별한 경우의 경계 처리에 유의하십시오.

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대 주요 순위입니다. 많은 관심과 사랑 부탁드립니다. 다음에 만나요


Supongo que te gusta

Origin blog.csdn.net/m0_74459304/article/details/131749022
Recomendado
Clasificación