算法实现之选择排序及优化

距上一次热血澎湃看算法已经过去两年了,如果不是看到马老师最近开始讲算法了估计还会继续遗忘下去。

先备份一张图,来源:http://www.mashibing.com(欢迎大家访问马老师网站)

 这次来学习选择排序,顾名思义,选择排序当然是选择为主。举例说明。

5 6 1 4 3
    ↑

选择这几个数中的最小(大)的数:1,放到最前(后)面即和首(尾)位交换

1 6 5 4 3
        ↑

然后再除了1之后选择最小的数:3,放到第二的位置。

1 3 5 4 6
      ↑

循环下去,最后得到1 3 4 5 6的数组即可。

用代码实现:

//选择排序
void selectSort(int num[],int len)
{
	int i = 0,j = 0;
	int minIndex = 0;
	
	for(i = 0;i < len;i++)
	{
		//假设首部数值为最小值 
		minIndex = i;
		for(j = i + 1;j < len;j++)
		{
			if(num[j] < num[minIndex])
			{
				//找到最小的值 
				minIndex = j;
			}
		}
		//把最小的值和首部进行交换 
		int temp = num[i];
		num[i] = num[minIndex];
		num[minIndex] = temp;
	}
}

非常简单易懂的算法,但是由马老师的图可知该算法是不稳定的,什么叫做不稳定呢?就是两个相等的值,在排序之后位置可能发生变化,让我们写个例程测试一下

struct student
{
	int age;
	char name[20];
};

void main()
{	
	int i = 0,j = 0,minIndex = 0; 
	//注意在初始化的时候,有两个结构体的age都是4,但是test4 first位置在 test4 last前面 
	struct student all[] = {
		{4,"test4 first"},
		{3,"test3"},
		{4,"test4 last"},
		{2,"test2"},
		{1,"test1"},
	};
	
	for(i = 0;i < 5;i++)
	{
		minIndex = i;
		for(j = i + 1;j < 5;j++)
		{
			if(all[j].age < all[minIndex].age)
			{
				minIndex = j;
			}
		}
		struct student temp = all[minIndex];
		all[minIndex] = all[i];
		all[i] = temp;
	}
	for(i = 0;i < 5;i++)
	{
		printf("%s,",all[i].name);
	}
	printf("\n");
}

运行结果

test1,test2,test3,test4 last,test4 first

很明显,test4 first和test4 last的位置反了,这就是不稳定造成的结果,举个例子,A现在银行存了5万,B也在银行存了5万,理论上查询存5万的第一人应该是A,但是经过这个排序算法之后查询到的却是B,后面的问题可想而知。

优化:马老师提示其中一种优化方法可以是一次找到最大值和最小值,把最小值放到前面,最大值放到后面不就把循环次数缩小一半了吗。

用代码实现:

//优化选择排序
void selectSortFindMinAndMax(int num[],int len)
{
	int i = 0,j = 0,k = 0,m = 0;
	int minIndex = 0,maxIndex = 0;
	
	for(i = 0;i < len / 2;i++)
	{
		minIndex = i;
		maxIndex = i;
		for(j = i + 1;j < len - k;j++)
		{
			if(num[j] < num[minIndex])
			{
				//找到最小值 
				minIndex = j;
			}
			if(num[j] > num[maxIndex])
			{
				//找到最大值 
				maxIndex = j;
			}
		}
		//考虑如果最大值就是i下标,如果先交换最小值,则会把最大值位置的数值改变
		//这种情况应该先交换最大值。 
		if((maxIndex == i) && (minIndex != len - k - 1))
		{
			swap(num,len - k - 1,maxIndex);
			swap(num,i,minIndex);
		}
		//考虑如果最大值是i下标,最小值是循环尾,则只用交换一次即可。 
		else if((maxIndex == i) && (minIndex == len - k - 1))
		{
			swap(num,maxIndex,minIndex);
		}
		//其余情况先交换最小值,再交换最大值 
		else
		{
			swap(num,i,minIndex);
			swap(num,len - k - 1,maxIndex);
		}
		//循环一次之后,循环尾向前移 
		k++;
	}
}

两种方法的循环次数以及时间比较(数组大小10000,有rand()随机产生):

系统函数0.001000ms,普通选择0.111000ms循环49995000次,优化选择0.083000ms循环25000000次
系统函数0.002000ms,普通选择0.115000ms循环49995000次,优化选择0.071000ms循环25000000次
系统函数0.002000ms,普通选择0.112000ms循环49995000次,优化选择0.077000ms循环25000000次
系统函数0.002000ms,普通选择0.106000ms循环49995000次,优化选择0.081000ms循环25000000次
系统函数0.001000ms,普通选择0.111000ms循环49995000次,优化选择0.077000ms循环25000000次
系统函数0.001000ms,普通选择0.119000ms循环49995000次,优化选择0.093000ms循环25000000次
系统函数0.002000ms,普通选择0.113000ms循环49995000次,优化选择0.088000ms循环25000000次
系统函数0.001000ms,普通选择0.110000ms循环49995000次,优化选择0.074000ms循环25000000次
系统函数0.001000ms,普通选择0.112000ms循环49995000次,优化选择0.075000ms循环25000000次
系统函数0.001000ms,普通选择0.114000ms循环49995000次,优化选择0.076000ms循环25000000次