[정렬] 병합 정렬(자세한 재귀 + 비재귀 그림)

소개

이 기사에서는 정렬 알고리즘인 병합 정렬을 계속 소개합니다.
병합 정렬은 두 개의 정렬된 시퀀스를 하나의 정렬된 시퀀스로 병합하는 병합이라는 아이디어를 사용합니다. 두 개의 정렬된 연결 목록의 이전 병합에서 이 아이디어가 사용되었습니다 .

병합 정렬

생각의 기차

병합 정렬은 병합된 데이터의 임시 저장을 위해 배열과 동일한 크기의 공간이 필요합니다.

정렬할 때 먼저 전체 배열을 2개의 동일한 부분으로 나눈 다음 4개의 부분으로 나눕니다...등등하게 더 이상 나눌 수 없을 때까지(간격에 하나의 요소만 남음) 위쪽으로 병합합니다. 즉,
임시 공간 위치에서 두 간격을 해당 간격으로 병합한
다음 임시 배열의 해당 간격에서 정렬된 데이터를 다시 원래 배열로 복사하고
정렬이 완료될 때까지 위쪽으로 병합합니다.
(참고로 여기서 이등분은 동시에 수행되지 않고 재귀에서는 한 줄씩 수행된다. 왼쪽이 머리까지 재귀한 후 다시 상위 수준으로 돌아가 계속 재귀한다. 마지막에서 두 번째 수준의 오른쪽으로, 오른쪽은 끝으로 돌아갑니다. 상위 수준에서 왼쪽 아래 간격과 오른쪽 간격의 병합을 실행합니다. 병합 후 끝에서 두 번째 세 번째 계층으로 돌아가고, 그런 다음 레이어의 오른쪽으로 재귀...)

여기에 이미지 설명 삽입

재귀 구현

퀵정렬의 재귀적 방식과 달리 퀵정렬은 가장 큰 구간에서 연산을 끝낸 후 아래쪽으로 재귀하여 정렬이 끝날 때까지 좌우 구간에서 연산을 하고, 정렬은 가장 작은 구간까지 먼저 재귀하고 그 다음 정렬하는 방식이다. 위쪽으로 병합합니다. 따라서 퀵 정렬을 구현할 때 각 레이어의 연산이 먼저이고 함수 재귀가 마지막이고, 퀵 정렬은 재귀가 먼저이고 각 레이어의 작업이 마지막입니다.

함수에는 원래 배열, 간격의 왼쪽 및 오른쪽 첨자, 임시 공간의 4개 매개 변수가 있습니다.
함수에 전달된 초기 매개변수는 전체 배열이며 재귀적으로 아래쪽으로 시작>=끝일 때 return은 재귀를 종료하고
midi를 생성하고 간격의 중간 위치를 기록한 다음 왼쪽 및 오른쪽 간격을 각각 재귀합니다.

그런 다음 간격에 대한 작업을 실현합니다.
먼저 begin1, end1, begin2, end2를 사용하여 병합할 간격의 왼쪽 및 오른쪽 간격의 시작 및 끝 첨자를 기록하고 k를 사용하여 간격의 해당 시작 위치를 기록합니다. 임시 공간 temp에 병합한 다음 왼쪽 및 오른쪽
간격을 임시 임시 공간에 병합하고
마지막으로 해당 임시 공간의 데이터를 다시 복사합니다.

여기에 이미지 설명 삽입

병합 프로세스의 경우 두 간격의 데이터가 차례로 비교되고 더 작은 요소가 임시 공간에 테일 삽입됩니다.

여기에 이미지 설명 삽입

void _MergeSort(int* a, int begin, int end, int* temp)
{
    
    
	if (begin >= end)
	{
    
    
		return;
	}
	int midi = (begin + end) / 2;
	_MergeSort(a, begin, midi, temp);
	_MergeSort(a, midi+1, end, temp);
	
	int begin1 = begin, end1 = midi;
	int begin2 = midi+1, end2 = end;
	int k = begin1;
	while (begin1 <= end1 && begin2 <= end2)
	{
    
    
		if (a[begin1] < a[begin2])
		{
    
    
			temp[k++] = a[begin1++];
		}
		else
		{
    
    
			temp[k++] = a[begin2++];
		}
	}
	while (begin1 <= end1)
	{
    
    
		temp[k++] = a[begin1++];      
	}
	while (begin2 <= end2)
	{
    
    
		temp[k++] = a[begin2++];
	}

	memcpy(a + begin, temp + begin, sizeof(int)*(end - begin + 1));
}

void MergeSort(int* a, int n) //递归实现
{
    
    
	int left = 0;
	int right = n - 1;
	int* temp = (int*)malloc(sizeof(int) * n);
	if (temp == NULL)
	{
    
    
		perror("malloc");
		return;
	}
	_MergeSort(a, left, right, temp);
	free(temp);
}

비재귀 정렬

생각의 기차

비재귀적으로 정렬을 구현하면 퀵 정렬과 달리 이전 레이어의 간격을 기록할 필요가 없고 일정 간격을 병합한 후 원래 배열로 돌아가면 충분하다. 그래서 우리는 배열을 그룹화하고 각 두 그룹의 값을 병합하면 됩니다. 각 그룹의 요소 수는 1부터 시작합니다. 각 루프가 끝나면 루프가 끝날 때까지 각 그룹의 요소 수에 2를 곱합니다. 배열의 길이보다 큽니다.

루프의 각 레이어에서 이 레이어의 두 간격을 모두 그룹으로 병합하고 이 데이터를 원래 배열로 다시 전송해야 합니다.

(그림)

성취하다

비재귀를 구현할 때 먼저 임시 공간 온도를 동적으로 연
다음 각 레이어에서 병합해야 하는 각 간격의 요소 수만큼 간격을 만들고 1로 초기화합니다.

외부 while 루프는 간격 값을 제어하며 배열의 요소 수가 요소 수보다 크거나 같으면 루프가 종료되고 내부 for 루프는 각 레이어(2개의 셀)에서 병합할 그룹을 제어합니다
. 간격이 요소수가 그룹으로 병합됨):
왼쪽 구간의 시작 위치 begin1은 i, 끝 위치 end1은 begin1+gap-1, 오른쪽 구간의 시작 위치 begin2는 end1+1, 종료 위치 end2는 시작2+간격-1이고, k는 온도에서 해당 시작 위치 시작 위치의 첨자입니다.
그런 다음 이 간격 그룹이 범위를 벗어나는지 판단해야 합니다. for 루프는 i<n만 제어하므로, 즉 begin1<n, end1, begin2 및 end2는 모두 경계를 넘을 수 있으며,
end1 또는 begin2가 경계를 넘으면 이 그룹(왼쪽 및 오른쪽) 간격이 하나씩 있음을 의미합니다. 더 적은 간격이므로 병합할 필요가 없습니다. end2가 경계를 넘으면 왼쪽과 오른쪽 간격의 집합이 존재하고 병합해야 함을 의미하므로 end2를 배열의 마지막 요소의 첨자로 변경하면 됩니다.

그런 다음 왼쪽 및 오른쪽 간격을 temp의 해당 위치로 병합합니다.
각 그룹(왼쪽 및 오른쪽) 간격이 병합된 후 배열을 원래 배열로 다시 복사합니다.
[이전 end2가 범위를 벗어날 수 있으므로 주의해야 합니다. , 2*gap 데이터를 직접 복사할 수 없지만 end2-i+1 데이터여야 함]

여기에 이미지 설명 삽입

void MergeSortNonR2(int* a, int n) //非递归实现(分别转移)
{
    
    
	int gap = 1;
	int* temp = (int*)malloc(sizeof(int) * n);
	if (temp == NULL)
	{
    
    
		perror("malloc");
		return;
	}

	while (gap < n)
	{
    
    
		for (int i = 0; i < n; i += (gap * 2))//每层
		{
    
    
			int begin1 = i, end1 = begin1 + gap - 1;
			int begin2 = end1 + 1, end2 = begin2 + gap - 1;
			int k = begin1;

			//判断是否越界(如果越界,直接break跳过归并)
			if (end1 >= n || begin2 >= n)
			{
    
    
				break;
			}
			else if (end2 >= n)
			{
    
    
				end2 = n - 1;
			}

			while (begin1 <= end1 && begin2 <= end2)
			{
    
    
				if (a[begin1] < a[begin2])
				{
    
    
					temp[k++] = a[begin1++];
				}
				else
				{
    
    
					temp[k++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
    
    
				temp[k++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
    
    
				temp[k++] = a[begin2++];
			}

			memcpy(a + i, temp + i, (end2 - i + 1) * sizeof(int));//每次归并都转移
		}

		gap *= 2;//走向下一层
	}
}

요약하다

이쯤에서 머지 정렬에 대한 내용이 소개되었고 정렬 알고리즘에 대한 설명도 끝이 났는데,
제가 어떤 부분을 명확하게 소개하지 않았거나 어떤 부분에 문제가 있다고 생각되시면, 의견 영역에서 제기하는 것을 환영합니다.

이 글이 도움이 되셨다면 클릭 한번으로 연결되길 바랍니다

당신과 함께 발전하기를 바랍니다

추천

출처blog.csdn.net/weixin_73450183/article/details/130319055