一、选择排序
(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()
在选择排序中,每趟都会选出最大元素与最小元素,然后与两端元素交换,此时,待排序序列中如果存在与原来两端元素相等的元素,稳定性就可能被破坏。如[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() ,平均时间复杂度是O()。
在冒泡排序中,遇到相等的值,是不进行交换的,只有遇到不相等的值才进行交换,所以是稳定的排序方式。
三、插入排序
(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()。
平均来说,array[1…j-1]中的一半元素小于array[j],一半元素大于array[j]。插入排序在平均情况运行时间与最坏情况运行时间一样,是O()。
在使用插入排序时,元素从无序部分移动到有序部分时,必须是不相等(大于或小于)时才会移动,相等时不处理,所以直接插入排序是稳定的。
四、简单位运算
(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);
}