(1)简单排序算法详解(C语言数据结构及算法)

 本文参考:http://t.csdn.cn/vDEcy

一、选择排序

(1)工作原理:

第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置(或末尾位置),然后再从剩余的未排序元素中寻找到最小(大)元素,继续放在起始位置(末尾位置),直到未排序元素个数为0。

(2)代码实现:

/*交换数组中两元素位置*/
void swap(int* arr, int i, int j)
{
	int tmp = arr[i];
	arr[i] = arr[j];
	arr[j] = tmp;
}

/*选择排序*/
void selectsort(int* arr,int len)//arr为待排序数组,len为数组内元素个数
{
	if (arr == NULL || len < 2)//边界检查,当数组为空或者数组只有一个元素时,直接输出结果
		return;
	for (int i = 0; i < len - 1; i++)
	{
		int minIndex = i;
		for (int j = i + 1; j < len; j++)
		{
			if (arr[j] < arr[minIndex])
			{
				minIndex = j;
			}
		}
		swap(arr, i, minIndex);
	}
	for (int k = 0; k < len; k++)
	{
		printf("%d ", arr[k]);
	}
}

(3)优化选择排序算法

每一次排序时分别选出未排序元素中最大项和最小项,并将其分别置于序列起始位置和末尾位置。这样可以使得循环轮数减少一半,运行速度减少一半。

/*选择排序优化*/
void selectsort_plus(int* arr, int len)
{
	if (arr == NULL || len < 2)//边界检查,当数组为空或者数组只有一个元素时,直接输出结果
		return;
	int left = 0, right = len - 1;
	while (left < right)
	{
		int min = left, max = right;
		for (int i = left; i <= right; i++)
		{
			if (arr[i] < arr[min])
				min = i;
			if (arr[i] > arr[max])
				max = i;
		}
		swap(arr, max, right);
		if (min == right)//如果最小值被前一轮交换移动过
			min = max;
		swap(arr, min, left);
		left++, right--;
	}
	for (int k = 0; k < len; k++)
	{
		printf("%d ", arr[k]);
	}
}

(4)时间复杂度和稳定性

两轮嵌套循环,故时间复杂度为O(n^2)

在选择排序中,每趟都会选出最大元素与最小元素,然后与两端元素交换,此时,待排序序列中如果存在与原来两端元素相等的元素,稳定性就可能被破坏。如[5,3,5,2,9],在array[0]与array[3]元素交换时,序列的稳定性就被破坏了,所以选择排序是一种不稳定的排序算法。


二、冒泡排序

(1)工作原理

相邻的元素两两比较,较大的数下沉(往后移动),较小的数冒起来(往前移动),这样一趟比较下来,最大(小)值就会排列在一端。整个过程如同气泡冒起,因此被称作冒泡排序。

(2)代码实现

/*使用位运算交换数组中两元素的位置*/
void swap1(int* arr, int i, int j)//i和j指向一个位置时会出错
{
	arr[i] = arr[i] ^ arr[j];
	arr[j] = arr[i] ^ arr[j];
	arr[i] = arr[i] ^ arr[j];
}

/*冒泡排序*/
void bubblesort(int* arr, int len)
{
	if (arr == NULL || len < 2)//边界检查,当数组为空或者数组只有一个元素时,直接输出结果
		return;
	for (int i = len - 1; i > 0; i--)//每经历一次大循环,最大的数n沉到队尾,下次循环考虑n前面的所有数
	{
		for (int j = 0; j < i; j++)//每次小循环比较相邻的两个数,将较大数后移
		{
			if (arr[j] > arr[j + 1])
				swap1(arr, j, j + 1);
		}
	}
	for (int k = 0; k < len; k++)
	{
		printf("%d ", arr[k]);
	}
}

(3)优化冒泡排序算法

1.循环优化 :分为内循环优化和外循环优化,作用是如果在排序的中间过程中序列已经有序时,提前终止排序进程。

2.双向遍历冒泡排序(鸡尾酒排序):从正向和逆向同时开始冒泡排序,每次排序过程中同时实现最大数下沉和最小数上浮。

/*鸡尾酒排序*/
void presort(int* arr, int len,int preindex)//从前往后排序
{
	for (int i = preindex + 1; i < len; i++)
	{
		if (arr[preindex] > arr[i])//将最小项放在preindex位置上
			swap1(arr, preindex, i);
	}
}
void backsort(int* arr, int len, int backindex)//从后往前排序
{
	for (int i = backindex - 1; i >= 0; i--)
	{
		if (arr[backindex] < arr[i])//将最大项放在backindex位置上
			swap1(arr, backindex, i);
	}
}
void bubblesort_plus(int* arr, int len)
{
	int preindex = 0, backindex = len - 1;
	while (preindex < backindex)
	{
		presort(arr, len, preindex);
		preindex++;
		if (preindex >= backindex)
			break;
		backsort(arr, len, backindex);
		backindex--;
	}
	for (int k = 0; k < len; k++)
	{
		printf("%d ", arr[k]);
	}
}

(4)时间复杂度和稳定性

冒泡排序最好的时间复杂度为O(n),最坏时间复杂度为O(n^2) ,平均时间复杂度是O(n^2)。

在冒泡排序中,遇到相等的值,是不进行交换的,只有遇到不相等的值才进行交换,所以是稳定的排序方式。 


 三、插入排序

(1)工作原理

 将初始数据分为有序部分和无序部分,每一步将一个无序部分的数据插入到前面已经排好序的有序部分中,直到插完所有元素为止。

(2)代码实现

/*交换数组中两元素位置*/
void swap(int* arr, int i, int j)
{
	int tmp = arr[i];
	arr[i] = arr[j];
	arr[j] = tmp;
}

/*插入排序*/
void insertionsort(int* arr, int len)
{
	if (arr == NULL || len < 2)//边界检查,当数组为空或者数组只有一个元素时,直接输出结果
		return;
	for (int i = 1; i < len; i++)//0~i做到有序,i~len为无序
	{
		for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--)//从右往左,依次比较,将数据插入,当插入到正确位置时停止
		{
			swap(arr, j, j + 1);
		}
	}
	for (int k = 0; k < len; k++)
	{
		printf("%d ", arr[k]);
	}
}

(3)优化插入排序算法

 由于逐次交换寻找插入位置过于浪费时间,所以这里采用折半寻找的方式提前确定插入位置,然后再将待插入元素放入准备好的位置即可。

/*插入排序优化——折半插入排序*/
void insertionsort_plus(int* arr, int len)
{
	int low, high, mid;
	int j, temp;
	for (int i = 1; i < len; i++)
	{
		low = 0;
		high = i - 1;
		temp = arr[i];
		while (low <= high)//折半寻找合适的插入点high+1
		{
			mid = (low + high) / 2;
			if (arr[mid] <= temp)
				low = mid + 1;
			if (arr[mid] > temp)
				high = mid - 1;
		}

		for (j = i - 1; j >= high + 1; j--)//将插入点high+1后面的元素全部后移一位,留出一个空位
		{
			arr[j + 1] = arr[j];
		}
		arr[j + 1] = temp;//将i号元素放入留出的空位中(由于前面多减了一个1,这里要加回来)
	}
	for (int k = 0; k < len; k++)
	{
		printf("%d ", arr[k]);
	}
}

(4)时间复杂度和稳定性

插入排序中最优的情况:当待排序序列是有序时,只需当前数跟前一个数比较一下就可以了,这时一共需要比较n- 1次,时间复杂度为O(n)。

最坏的情况:待排序数组是逆序的,此时需要比较次数最多,总次数记为:1+2+3+…+N-1,所以,插入排序最坏情况下的时间复杂度为O(n^2)。

平均来说,array[1…j-1]中的一半元素小于array[j],一半元素大于array[j]。插入排序在平均情况运行时间与最坏情况运行时间一样,是O(n^2)。

在使用插入排序时,元素从无序部分移动到有序部分时,必须是不相等(大于或小于)时才会移动,相等时不处理,所以直接插入排序是稳定的。


四、简单位运算

(1)若一个数组中存在一些数,其中有一个数出现了奇数次,其他数都出现了偶数次,使用时间复杂度为O(n),空间复杂度为O(1)的算法求出现了奇数次的数。

思路:将这些数全部异或在一起,其中出现偶数次的数异或以后为0,出现奇数次的数异或以后还是这个数,0与这个数异或以后还是这个数。

/*寻找出现奇数次的数*/
void printOddTimesNum1(int* arr, int len)
{
	int eor = 0;
	for (int i = 0; i < len; i++)
	{
		eor = eor ^ arr[i];//将所有数异或
	}
	printf("%d", eor);
}

(2)若一个数组中存在一些数,其中有两个数出现了奇数次,其他数都出现了偶数次,使用时间复杂度为O(n),空间复杂度为O(1)的算法求出现了奇数次的数。

思路: 假设a和b为出现奇数次的两个数,那么将数组内所有数异或以后的结果就是eor = a^b;又因为a和b为两个不同的数,所以a和b的二进制形式里一定有某一位不同,例如a=11011和b=11001,a^b=00010,第二位不同。已知这个结论后,我们可以将数组中的所有数分成两组,第二位为1的所有数分为一组,第二位为0的分为一组,这样我们就将a和b分到了两组,每组中就只有一个出现奇数次的数,运用第一题的结论,可以轻松求出a和b。

/*寻找两个出现奇数次的数*/
void printOddTimesNum2(int* arr, int len)
{
	int eor = 0;
	for (int i = 0; i < len; i++)
		eor = eor ^ arr[i];
	//eor = a^b

	int rightOne = eor & (~eor + 1);//eor中1出现的位置(最右侧)
	int onlyOne = 0;
	for (int j = 0; j < len; j++)
	{
		if ((arr[j] & rightOne) == rightOne)
		{
			onlyOne ^= arr[j];
		}	
		//onlyOne为a或者b
	}
	printf("%d %d", onlyOne, onlyOne ^ eor);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值