数据排序 归并排序,计数排序以及快速排序的三路优化

数据排序 归并排序,计数排序以及快速排序的三路优化

之前的排序算法讲解: 部分排序算法讲解_sleepymonstergod的博客-CSDN博客

image-20220910120234540

1.归并排序

在开始归并排序之前,我们先进行一个和归并排序类似思想的题目

21. 合并两个有序链表

image-20220910120759030

思路:两个链表的节点一一对比,将数值小的放于新的升序链表中,然后依次进行该操作.

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
struct ListNode* tmp1=list1;
struct ListNode* tmp2=list2;
struct ListNode*guard=(struct ListNode*)malloc(sizeof(struct ListNode));
guard->next=NULL;
struct ListNode*tmp=guard;
if(list1==NULL)
{
    return list2;
}
if(list2==NULL)
{
    return list1;
}
while(tmp1 && tmp2)
{
    if(tmp1->val>tmp2->val)
    {
        tmp->next=tmp2;
        tmp2=tmp2->next;
    }
    else
    {
        tmp->next=tmp1;
        tmp1=tmp1->next;
    }
    tmp=tmp->next;
}
if(tmp1)
{
    tmp->next=tmp1;
}
if(tmp2)
{
    tmp->next=tmp2;
}
tmp=guard->next;
free(guard);
return tmp;
}

归并排序也是这种思想

归并排序思路:

先分解

image-20220910121900064

后合并

image-20220910122255522

归并排序的核心思想是分治,我们需要将一个完整的数组先进行拆分,随后合并,合并的过程其实就是交换索引内容的过程,该过程中可以找到很多逆序对,在解决逆序对问题时可以考虑归并排序。

在合并过程中,不需要两边数据个数一样,只要两边都存在就可以.

归并排序在排序过程中,需要一个额外的数组tmp来进行合并操作,不推荐直接在原数组修改,会出现bug,且操作复杂.

归并排序实现

void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin >= end)
	{
		return;
	}
	int mid = (end - begin) / 2 + begin;
	

	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid+1, end, tmp);

	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin;
	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++];
	}
	memcpy(a+begin,tmp+begin,sizeof(int)*(end-begin+1));
}
void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	_MergeSort(a,0,n-1,tmp);
	free(tmp);
	tmp = NULL;
}

image-20220910122840861

归并排序的非递归实现

当归并排序非递归实现时,直接使用gap=1,gap=2,gap=4…来实现归并排序.

在使用时需要注意边界问题(边界处理情况在代码注释中解释)

代码实现

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	int gap = 1;
	while (gap<n)
	{
		for (int j = 0; j < n; j += 2 * gap)
		{
			int begin1 = j, end1 = j + gap - 1;
			int begin2 = j + gap, end2 = j + 2 * gap - 1;
			int i = j;
			int begin = begin1;
			int end = end2;

			if (end1 >= n)
			{
				break;
			}//当end1越界时,即剩下一组数据,直接停止归并,因为后面没有归并的数据
			if (begin2 >= n)
			{
				break;
			}//当begin2越界时,即剩下一组数据,直接停止归并,因为后面没有归并的数据
			if (end2 >= n)
			{
				end2 = n - 1;
				end = end2;
			}//当end2越界时,说明剩下两组数据,需要进行归并,将end2的值改为n-1可以保证不越界

			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++];
			}
			memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
		}
		gap *= 2;
	}
	
}

image-20220910123517227

2.计数排序

计数排序是非比较类排序算法,当数据比较集中时,使用计数排序会有很高的时间效率

实现原理 统计数组中各个数字出现的次数,再依据次数,按照一定的排序要求进行排序.

实现代码

此处,该代码只能排序20以内的数字.

void CountSort(int* a, int n)
{
	int* tmp = (int*)calloc(20,sizeof(int));
	int i = 0;
	for ( i = 0; i < n; i++)
	{
		tmp[a[i]]++;
	}
	i = 0;
	int loc = 0;
	while (i < 20)
	{
		if (tmp[i] == 0)
		{
			i++;
		}
		else
		{
			a[loc++] = i;
			tmp[i]--;
		}
	}
	free(tmp);
}

image-20220910124158520

3.快速排序的三路优化

当数据中重复数据比较多时,快速排序的时间复杂度就会达到o(n^2),时间效率极低 ,例如

image-20220910124538383

image-20220910124732086

第一次运行100w个20以内的数排序,快速排序直接爆栈.

第二次运行未快速排序递归方式参与,非递归的时间效率极低.

所以在此处,我们将对最开始的快速排序进行一些优化.此处选用三路优化的方法

实现模拟图:

image-20220910125406367

之前快速排序是分为小于等于v和大于等于v两部分,现在我们将其分为三个部分小于等于和大于,等于的部分不在参与后续排序过程,此处大大的提高了时间效率.

代码实现

void swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}


void InsertSort(int* a, int n)
{
	//[0,end]有序
	for (int i = 0; i <n-1; i++)
	{
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				end--;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}

}

int getminindex(int* a, int left, int right)
{
	int mid = (right - left) / 2 + left;
	if (a[mid] > a[left])
	{
		if (a[mid] <= a[right])
		{
			return mid;
		}
		else//a[mid]>a[right]
		{
			if (a[right] > a[left])
			{
				return right;
			}
			else
			{
				return left;
			}
		}
	}
	else //a[mid]<=a[left]
	{
		if (a[mid] >= a[right])
		{
			return mid;
		}
		else //a[mid] <a[right]
		{
			if (a[right] >= a[left])
			{
				return left;
			}
			else
			{
				return right;
			}
		}
	}
}


int* NewPartSort(int* a, int left, int right)
{
	int mid = getminindex(a, left, right);
	swap(a + left, a + mid);
	int less = left ;
	int more = right;
	int loc = less;
	int midnum = a[left];
	while (loc<=more)
	{
		while (a[less]<midnum)
		{
			less++;
		}
		while(a[more]>midnum)
		{
			more--;
		}
		if (a[loc] == midnum)
		{
			loc++;
		}
		else if (a[loc] < midnum)
		{
			swap(a + loc, a + less);
			less++;
		}
		else
		{
			swap(a + loc, a + more);
			more--;
		}
	}
	int* answer = (int*)malloc(sizeof(int) * 2);
	answer[0] = less;
	answer[1] = more;
	return answer;
}
void NewQuickSort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	if (right - left < 10)
	{
		InsertSort(a + left, right - left + 1);
	}
	else
	{
		int *keyi = NewPartSort(a,left,right);
		NewQuickSort(a, left, keyi[0]-1);
		NewQuickSort(a, keyi[1]+1, right);
		free(keyi);
	}
}

image-20220910151303785

100w个随机数

image-20220910151554047

100w个20以内的数 快速排序递归形式会爆栈,此处不再运行

image-20220910151945252

1000w个随机数

image-20220910153431511

100w个20以内的数 快速排序递归形式会爆栈,此处不再运行

image-20220910151945252

1000w个随机数

image-20220910153431511

1000w个20以内的数

image-20220910152150279


优化后的快速排序,当重复数字较多时,效率会很高,都在一般情况下,效率会比一般的快速排序低一点,但平均下来,我会更推荐三路优化版本,综合性能会超过普通版本的快速排序.

评论 27
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sleepymonstergod

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值