下面介绍几种排序算法,C语言中qsort虽然可以排序,但还是了解了解内部构造比较好。
1. 冒泡排序
冒泡排序是我最开始学习C语言时候,第一个接触的排序算法,说实话,第一次写出来的时候,感觉是很神奇的,但是第二天又忘了,也是真的很痛苦。
- j 从 i + 1的位置遍历数组,将最小的哪一个数据和 nums[i]交换即可。
//冒泡排序
void BubbleSort1(int* nums, int numsSize)
{
int i, j,count = 0;
for (i = 0; i < numsSize - 1; i++)
{
for (j = i + 1; j < numsSize; j++)
{
if (nums[i] > nums[j])
{
count++;
Swap(&nums[i], &nums[j]);
}
}
}
printf("BubbleSort1排序结束: count = %d 结果如下", count);
PrintNums(nums, numsSize);
}
上面是我在学习C语言中第一次学习冒泡排序时候别人教的,但是看上面那张图,
- 2本来就那么小,但是经过一次遍历,位置都没动,而下面的3就更离谱了,它竟然被移动到了最后。
- 而解决这个问题的办法就是,j从后往前遍历,使nums[j] = Max(nums[j],nums[j - 1]);
- 这样一来,就可以做到,每一次的用j遍历数组时候,会将大的永远靠后,小的永远靠前。
void BubbleSort2(int* nums, int numsSize)
{
int i, j, count = 0;
for (i = 0; i < numsSize - 1; i++)
{
for (j = numsSize - 1; j > i; j--)
{
if (nums[j - 1] > nums[j])
{
count++;
Swap(&nums[j-1], &nums[j]);
}
}
}
printf("BubbleSort2排序结束: count = %d 结果如下", count);
PrintNums(nums, numsSize);
}
冒泡排序的时间复杂度还是O(n^2)
2. 选择排序
选择排序,顾名思义就是靠选择来排序嘛。
- i 还是从 0 索引处开始,一点点的向后改变
- 而 j 的话还是和冒泡排序一样,从 i + 1 的位置开始,遍历一遍数组,而不一样的是,它不用去来回的交换数据,它只需要选择出那个最小就好了。
- 选出来后将其与nums[i]进行交换即可。
代码如下:
//选择排序
void SelectionSort(int* nums, int numsSize)
{
int i,j,count = 0;
for (i = 0; i < numsSize - 1; i++)
{
int minIndex = 0;
for (j = i + 1; j < numsSize; j++)
{
//选择最小的出来
if (nums[j] < nums[minIndex])
{
minIndex = j;
}
}
if (i != minIndex)
{
//交换最小的
Swap(&nums[i], &nums[minIndex]);
count++;
}
}
printf("SelectionSort 排序结束: count = %d 结果如下", count);
PrintNums(nums, numsSize);
}
而它的交换次数相比于冒泡排序减少了很多次,优化了不少。
但是其时间复杂度还是O(n^2)
3. 插入排序
- 默认索引0的位置已经排好,然后i从1的索引处开始,判断 i 是否比前一个小,
- 如果小的话,将nums[i] 利用一个tmp来存储,然后将将比tmp大的数,全部向后挪动,
- 直到找到合适的位置后,就停下来,插入即可
//插入排序
void InserctionSort(int* nums, int numsSize)
{
int i, j;
//i 从 1索引处开始,默认 0处已经排好了
for (i = 1; i < numsSize; i++)
{
//当前的数据,比前面小
if (nums[i] < nums[i - 1])
{
int tmp = nums[i];
//挪动数据
for (j = i - 1; nums[j] > tmp; j--)
{
nums[j + 1] = nums[j];
}
//找到合适的位置了
nums[j + 1] = tmp;
}
}
printf("InserctionSort 排序结束 结果如下");
PrintNums(nums, numsSize);
}
4. 希尔排序
希尔排序我个人认为它就是插入排序的进阶版,而它是怎样去执行的,它是以一个区间一个区间去排序的。
下面是第一层循环的图片:当前的 区间增量是 4,然后区间增量会是一个这样的变化过程。
9/2 = 4(第一次) 4/2 = 2(第二次) 2/2 = 1(最后一次)
那么最后一次时候增量是1,它不就变成了上面刚刚说过的插入排序嘛?
所以前面的其实也是插入排序,不过就是有间隔的插入排序而已
看下图的比较,将incremen 变成1 不久和插入排序一样了吗?
下面是代码。
void ShellSort(int* nums, int numsSize)
{
int increment = numsSize;
while (increment > 1)
{
increment /= 2;
int i, j, tmp, end;
for (i = 0, j = i + increment; j < numsSize; i++, j++)
{
tmp = nums[j];
for (end = i; end >= 0 && nums[end] > tmp; end -= increment)
{
nums[end + increment] = nums[end];
}
nums[end + increment] = tmp;
}
}## 标题
printf("ShellSort 排序结束 结果如下");
PrintNums(nums, numsSize);
}
其时间复杂度是O(n^(3/2))d
5. 归并排序
归并排序的话充分的利用分治的思想,这个东西是递归,文字实在是不好太好表达。
- 收件就是利用二分的原理,将要排序的区间越来越小,直到left == right(此时只有一个元素)
- 当数组有两个以上的数据时候,给数组进行排序,需要先利用一个tmp数组来存放拍好序的序列。
- 然后再拷贝回去。要注意拷贝时候的其实位置应该是从left开始。
- 然后一只递归下去就好。
代码如下:
void Merge(int* nums, int* tmpArr, int left, int mid, int right)
{
int i = left, j = mid+1, k = left; //i 代表mid左边的数组下标 j代表 mid 右边的数组下标, k则代表数组的开始位置,已经tmp数组的下标
//将有序序列纳入tmp数组
while (i < mid + 1 && j <= right)
{
if (nums[i] < nums[j])
{
tmpArr[k++] = nums[i++];
}
else
{
tmpArr[k++] = nums[j++];
}
}
//判断是否全部纳入
while (i < mid + 1)
{
tmpArr[k++] = nums[i++];
}
while (j <= right)
{
tmpArr[k++] = nums[j++];
}
//最后再拷贝回去
for (i = left; i < k; i++)
{
nums[i] = tmpArr[i];
}
}
void Msort(int* nums, int* tmpArr, int left, int right)
{
//数组中只有一个元素
if (left == right)
{
return;
}
int mid = (left + right) / 2;
Msort(nums, tmpArr, left, mid);
Msort(nums, tmpArr, mid + 1, right);
Merge(nums,tmpArr,left,mid,right);
}
void MergeSort(int* nums, int numsSize)
{
int* tmpArr = (int*)malloc(sizeof(int) & numsSize);
Msort(nums, tmpArr, 0, numsSize - 1);
printf("MergeSort 排序结束 结果如下");
PrintNums(nums, numsSize);
}
其时间复杂度是O(n * logn)
6. 快速排序
快速排序和前面的归并排序同样也是需要递归来实现的,在C语言中的qsort库函数就是快速排序。
- 首先确立一个key,一般是左边的第一个,或者右边的第一个都行,只是遍历的顺序不一样而已。
- 然后利用两个指针,left和right分别取找大值于小值
- right 先走, 当nums[right] < nums[key]的时候停下来(找小值)
- 找到之后left再取走,当nums[left] > num[key] 时候停下来(找大值)
- 然后去交换他俩,交换完成后重复上面的过程,直到left == right时候。
- 将nums[key] 和 nums[right(left)]交换
- 此时,左边全部小于key 右边全部大于key。
- 然后将左右两边分别用快排即可[begin,key - 1] [key + 1, end];
下面是代码:
void QSort(int* nums, int begin, int end)
{
if (begin >= end)
{
return;
}
int key = begin, left = begin, right = end;
while (left < right)
{
//找小的
while (left < right && nums[key] <= nums[right])
{
right--;
}
//找大的
while (left < right && nums[left] <= nums[key])
{
left++;
}
//交换两者
Swap(&nums[left], &nums[right]);
}
//于key进行交换
Swap(&nums[key], &nums[left]);
key = left;
//交换后,此时key左边的全部小于key右边的
QSort(nums, begin, key - 1);
QSort(nums, key+1, end);
}
void QuickSort(int* nums, int numsSize)
{
QSort(nums, 0, numsSize - 1);
printf("QuickSort 排序结束 结果如下");
PrintNums(nums, numsSize);
}
其时间复杂度是O(n * logn)