0
点赞
收藏
分享

微信扫一扫

基于Java-jsp的现代数字化城市公交查询系统论文(四)

夏天的枫_ 2024-04-30 阅读 12

目录

前言

一、排序的概念

1.1排序的概念

1.2 常见的排序算法

二、常见排序算法的实现

2.1 插入排序

2.2 希尔排序

2.3 选择排序

2.4 堆排序

2.5 冒泡排序

2.6 快速排序

2.6.1 hoare版本

2.6.2 前后指针版本

2.6.3 非递归版本

2.7 归并排序

归并排序

2.8 计数排序

三、算法性能

总结


前言

今天为我们来通过C语言来实现常见排序算法:直接插入排序、希尔排序、选择排序、堆排序、冒泡排序、快速排序、堆排序,了解算法的具体实现和算法的性能。


一、排序的概念

1.1排序的概念

排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次

序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排

序算法是稳定的;否则称为不稳定的。

内部排序:数据元素全部放在内存中的排序。

外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

1.2 常见的排序算法

二、常见排序算法的实现

2.1 插入排序

思想:把第一个元素当成有序,然后把下一个元素插入到这个有序序列里,使得保持有序,重复步骤我们可以n-1个元素排成有序,把第n个元素插入到有序序列里。

步骤:1.先把第一个元素当成有序

           2.取有序元素的最后下标end的下一个元素temp

           3.将temp从最后一个往前比较(end--),比temp大则元素往后移动,小则插入到end的后面。

           4.重复步骤2,3 循环n-1(n为数组长度)次

             

动画图解如下:

//插入排序
void InsertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++) 
	{
		int end=i;
		int temp = a[end + 1];
		while (end >= 0)
		{
            //比较与end位置元素大小
			if (temp < a[end])
			{
				a[end + 1] = a[end];
				end--;
			}
            //大于end位置元素,跳出
			else
			{
				break;
			}
		}
        //1.end大于零,temp放在end元素后一个
        //2.end小于零,temp也是放在end元素后,只不过此时为第一个元素
		a[end + 1] = temp;
	}
}

2.2 希尔排序

思想:插入排序最好的情况是有序,我们可以在插入排序之前进行预排序,使得序列变得接近有序

,先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序,重复上述步骤。然后排序就变得简单了。

步骤:1.我们定义一个gap为每组元素的之间的距离。

           2.把每个元素距离为gap的分为一组,进行插入排序

           3.gap减小(通常除2),再进行步骤2

           4.最后为gap减小到1,即每个元素各自为一组,即为插入排序

动画图解如下:

             

//希尔排序
void ShellSort(int* a, int n) {

	int gap=n;
	while (gap > 1) 
	{
		gap/=2;
        //循环到n-gap-1元素
		for (int i = 0; i < n-gap; i++)
		{
			int end = i;
			int temp = a[end + gap];
			while (end >= 0)
			{
				if (temp < a[end])
				{
					a[end + gap] = a[end];
					//end移动距离为gap
                    end -= gap;
				}
				else
				{
					break;
				}
			}
            //各自插入到每组元素
			a[end + gap] = temp;
		}
	}

}

1. 希尔排序是对 直接插入排序的优化

2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当 gap == 1 时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。

3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的希尔排序的时间复杂度都不固定

2.3 选择排序

思路:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。同时我们还可以更加精简算法,再选出最小的同时选出最大的,把最小的放在最左边,最大的放在最右边。

步骤:1.从头到尾遍历序列找出最大最小的值

           2.把最大最小值放在序列的尾和头

           3.序列向中间收,头加1,尾减1,重复1,2步骤

只选最小的选择排序的图解

动画图解如下:

//选择排序
void SelectSort(int* a, int n) {
	int begin = 0, end = n - 1;
	while (begin < end)
	{
		int maxi = begin, mini = begin;
		for (int i = begin + 1; i <= end; i++)
		{
			if (a[i] < a[mini])
			{
				mini = i;
			}
			if (a[i] > a[maxi])
			{
				maxi = i;
			}
		}
		Swap(&a[begin], &a[mini]);
		//避免二次交换
        if (begin == maxi)
			maxi = mini;
		Swap(&a[end], &a[maxi]);
		
        //缩小范围
		begin++;
		end--;

	}

}

直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用

2.4 堆排序

思路: 堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过 来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。

步骤:1.通过向下调整建堆( 排升序要建大堆,排降序建小堆)

           2.交换第一个和最后一个元素

           3.从零向下调整

           4.从堆的长度减一,开始重复步骤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[parent], &a[child]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}

	}
}
void HeapSort(int* a, int n) {
	
	//建堆
	for (int i = (n - 1 - 1) / 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--;
	}


}

堆排序使用堆来选数,效率就高了很多。

2.5 冒泡排序

思路:从头开始依次遍历整个序列,如果遇到大的就交换,最后我们就把大的交换到最后了。

步骤:1.遍历序列(遍历n-1次)

           2.与下一个进行比较,比后一个大的就交换。

动画图解如下:

//冒泡排序
void BubbleSort(int* a, int n) {
	for (int i = 0; i < n - 1; i++)
	{
		int exchang = 0;
		for (int j = 1; j < n - i; j++)
		{
            //前一个比后一个大就交换
			if (a[j] < a[j - 1])
			{
				Swap(&a[j], &a[j - 1]);
				exchang = 1;
			}
		}
		if (exchang == 0)
			break;
	}
}

2.6 快速排序

思路:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

选择基准值有多种方法:

1.直接选择头或者尾为基准值(但是序列为有序或者接近有序时间复杂度大)

2.随机数取基准值

//随机取基准值
int randi = rand() % (right - left + 1);
randi += left;
Swap(&a[left], &a[randi]);//基准值放到序列头

3.三位数取中

取序列头尾和中间三个数,让不是最大的,也不是最小的作为基准值。

//三位数取中
int midi = GetMidi(a, left, right);//取中间值函数
Swap(&a[midi], &a[left]);//基准值放到序列头

将区间按照基准值划分为左右两半部分的常见方式有:

2.6.1 hoare版本

思路:选择key(基准值),从尾向头开始找比key小的,然后再从头找比key大的,交换两者,然后继续整个步骤,直到它们两个相遇,交换key位置到相遇位置处,这样key前面就全为比key小的,后面全为比key大的,此时key为正确位置,然后分割序列,分为key前面的和key后面,重复上面所有步骤。

我们这直接选择最左边为key

步骤:1.最左边为key,定义left,right代表key下一个位置,序列尾

           2.让right向前走,找到比key小的,找到了停止

           3.让left向后走,找到比key大的,找到了停止

           4.交换此时righ和left的位置上的元素,重复2,3步骤

           5.直到相遇,交换此时相遇位置和key的。

           6.分割序列,分为keyi前面的序列和keyi后面的位置,重复以上所有步骤。

动画图解如下:

//快速排序 hoare版本
void QuickSort1(int* a, int left, int right) {
	//当只有一个元素和为空序列返回空
    if (left >= right)
		return;

	int begin = left, end = right;

    //取序列开头为key
	int keyi = left;
	
	while (left < right)
   	{    
        //找比key小的
		while (left < right && a[keyi] <= a[right])
		{
			right--;
		}
        //找比key大的
		while (left < right && a[keyi] >= a[left])
		{
			left++;
		}
        
		Swap(&a[left], &a[right]);
	}

	Swap(&a[left], &a[keyi]);
	keyi = left;

    //递归[begin,keyi-1] 和 [keyi+1,end]
	QuickSort1(a, begin, keyi - 1);
	QuickSort1(a, keyi+1, end);

}

2.6.2 前后指针版本

思路:选择最左或者最右为key,定义两个指针prev和cur分别指向序列开头,prev下一个位置。然后向后走,如果遇到比key小的cur走,prev也向后走。直到遇到比key大的,只有cur走,prev不走,当再次遇到比key小的,prev先走,然后交换此时cur和prev的值,cur再走。重复上面步骤,直到cur走到序列尾,然后交换prev和key的值。通过上面操作我们可以让cur和prev中间的值为比key大的,此方法就是把比key大的往后推,小的往前移。

步骤:1.选择最左key,prev和cur分别指向序列开头,开头下一个

           2.cur先走,prev后走

           3.遇到比key大的,只有cur走

          4.再遇到key小的,prev先走,然后交换此时prev和cur的值,cur在走

          5.cur走到序列尾,交换prev和key的值

          6..分割序列,分为keyi前面的序列和keyi后面的位置,重复以上所有步骤。

动画图解如下:

//快速排序
//前后指针法
void QuickSort2(int* a, int left, int right) {
	
	if (left >= right)
		return;

	
	int prev = left, cur = left+1;

	int keyi = left;
	while (cur <= right)
	{    
        //如果遇到比key小的才交换
		if (a[cur] < a[keyi] && ++prev != cur)
		{
			Swap(&a[cur], &a[prev]);
		}
		cur++;
	}
	Swap(&a[prev], &a[keyi]);
	keyi = prev;

    //分割[left,keyi-1] [keyi+1,right]
	QuickSort2(a, left, keyi - 1);
	QuickSort2(a, keyi+1, right);

}

2.6.3 非递归版本

思路:之前我们都是通过递归来解决的,我们需要的是排序的左右子区间,所以我们可以通过一个栈来存储排序的左右子空间,根据栈先进后出的特性。左右子区间依次进栈出栈从而达到递归的效果。

void QuickSortNonR(int* a, int left, int right) {
	ST st;
	StackInit(&st);
	
	//左右下标入栈
    //右边先进  左边后进
	StackPush(&st,right);
	StackPush(&st,left);

	while (!StackEmpty(&st))
	{

		left = StackTop(&st);
		StackPop(&st);

		right = StackTop(&st);
		StackPop(&st);

		//单趟
		int prev = left, cur = left + 1;

		int keyi = left;
		while (cur <= right)
		{
			if (a[cur] < a[keyi] && ++prev != cur)
			{
				Swap(&a[cur], &a[prev]);
			}
			cur++;
		}
		Swap(&a[prev], &a[keyi]);
		keyi = prev;

		//[left,key-1] && [key+1,right]
        
        //进左右子区间
		if (keyi + 1 < right)
		{
			StackPush(&st, right);
			StackPush(&st, keyi + 1);
		}

		if (keyi - 1 > left)
		{
			StackPush(&st, keyi - 1);
			StackPush(&st, left);
		}
	}
	
	StackDestroy(&st);

}

快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序

2.7 归并排序

思路:归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:

动画图解如下:

//归并排序
void _MergeSort(int* a, int begin, int end, int* temp) {
	//只有一个元素就返回
    if (begin == end)
		return;

    //从中间开始分割
	int mini = (begin + end) / 2;

    //指向两个序列起始位置
	int begin1 = begin, end1 = mini;
	int begin2 = mini + 1, end2 = end;

    
	_MergeSort(a, begin1, end1, temp);
	_MergeSort(a, begin2, end2, temp);
	
    //单趟
    //选下的插入到新的序列中
	int i = begin;//插入位置要保持相对位置
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2]) 
		{
			temp[i++] = a[begin1++];
		}
		else
		{
			temp[i++] = a[begin2++];
		}

	}
    
    //插入没有插入完的
	while (begin1 <= end1)
	{
		temp[i++] = a[begin1++];
	}

	while (begin2 <= end2)
	{
		temp[i++] = a[begin2++];
	}

    //拷贝位置也要保持相对位置
	memcpy(a + begin, temp + begin, sizeof(int) * (end - begin + 1));

}

void MergeSort(int* a, int n) {

    //开辟新的序列
	int* temp = (int*)malloc(sizeof(int) * n);
	if (temp == NULL)
	{
		perror("malloc fail");
		return;
	}
	
	_MergeSort(a, 0, n-1, temp);

	free(temp);
	temp = NULL;

}

非递归版本

我们之前是用递归先进行单个排序,在到组排。我们也可以不通过递归,通过回溯的思想,先定义一个gap,gap从1开始,即先单个为一组,然后两两排序,然后进行循环gap×2来进行gap数量为一组在进行归并。

void MergeSortNonR(int* a, int n)
{
	int* temp = (int*)malloc(sizeof(int) * n);
	if (temp == NULL)
	{
		perror("malloc fail");
		return;
	}

	int gap = 1;

	while (gap < n) 
	{
		for (int j = 0; j < n; j += gap * 2)
		{
            //两个区间的起始位置
			int begin1 = j, end1 = j + gap - 1;
			int begin2 = begin1 + gap, end2 = begin2 + gap - 1;
			
            //判断是否有越界

			if (end1 >= n || begin2 >= n)
				break;

			if (end2 >= n)
				end2 = n - 1;
			
			int i = j;

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

			}

			while (begin1 <= end1)
			{
				temp[i++] = a[begin1++];
			}

			while (begin2 <= end2)
			{
				temp[i++] = a[begin2++];
			}

            //相对位置
			memcpy(a + j, temp + j, sizeof(int) * (end2 - j + 1));
		}
		gap *= 2;
	}
	
	free(temp);
	temp = NULL;
}

2.8 计数排序

上面的排序,我们都是通过比较来排序的,而计数排序是非比较排序算法。

思路:遍历序列,找到最大最小的值,通过它们的差值来新开一个序列,在遍历序列,通过序列的值减去最小值作为下标在新开序列的位置计数。最后在遍历序列,该新序列有值则新序列的下标加最小值即为该序列上的值。

//计数排序
void CountSort(int* a, int n) {

	int max = a[0], min = a[0];

	for (int i = 0; i < n; i++)
	{
		if (a[i] > max)
			max = a[i];
		if (a[i] < min)
			min = a[i];
	}

	int range = max - min + 1;
	int* count = (int*)malloc(sizeof(int) * range);
	if (count == NULL)
	{
		perror("malloc fail");
		return;
	}

	memset(count, 0, sizeof(int) * range);

	//计数
	for (int i = 0; i < n; i++)
	{
		count[a[i] - min]++;
	}

	//排序
	int j = 0;
	for (int i = 0; i < n; i++)
	{
		while (count[i]--)
		{
			a[i] = i + min;
		}
	}
}

三、算法性能

算法性能再不同场景下有不一样的性能,下图参考:


总结

上述文章,我们介绍了各种排序算法,希望对你有所帮助。

举报

相关推荐

0 条评论