排序算法最重要的就是自己的思路,那我们必须要理清自己的思路,了解它每一步怎么走,有什么规律,边界情况是怎么控制的等等。接下来我会详细的介绍后四种排序算法,分别是:快速排序(三种方法实现)、归并排序、计数排序、直接选择排序。
github网址:
快速排序:
1) 挖坑法
让begin和end一个在数组前面,一个在数组后面,同时让pivot在最前面,再找一个关键字key,接下来让end往前找比key小的数字然后放入pivot中,然后pivot在换到end处,begin在往后找比key大的数字,找到后再和pivot换,pivot换到begin处,依次循环,直到begin和end相遇,然后让pivot换到它们相遇处,再让key入坑pivot。这样遍历一遍可以确定一个数据的确切位置,这个时候数组可以认为让pivot分为左区间和右区间,然后就可以去递归左右区间,直到剩一个数就可以认为它是有序的,然后往后返回,一次确定一个数,直到确定完整个数组,即整个数组全部有序。
可以参考这张图进行理解
代码实现(全都有详细注释)
这里采用了三数取中的方法,(最前面,最后面和中间数的数据中间值)减少采用左右端点碰到极端顺序的出现的最坏情况( 当选取左右端点,碰到数据有序,从大到小或是从小到大的情况 ,算法时间复杂度就会变成最坏时间复杂度)。
void swep(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
//三数取中,找最中间的数
int GetMidIndex(int* arr, int left, int right)
{
int mid = (left + right) >> 1;//找中间数
if (arr[mid] < arr[right])
{
if (arr[left] > arr[right])
{
return right;
}
else if (arr[left] < arr[right] && arr[left] > arr[mid])
{
return left;
}
else
{
return mid;
}
}
else
{
if (arr[left] < arr[right])
{
return right;
}
else if (arr[left] > arr[right] && arr[left] < arr[mid])
{
return left;
}
else
{
return mid;
}
}
}
void QuickSort(int* arr, int left, int right)
{
if (left >= right)//剩一个数认为有序,返回
{
return;
}
int index = GetMidIndex(arr, left, right);//三位取中,防止最坏情况
swep(&arr[left], &arr[index]);//将取到的数放到最前面
int begin = left;
int end = right;
int pivot = left;
int key = arr[begin];//关键字
while (begin < end)
{
while (begin<end && arr[end] >= key)//找小
{
end--;
}
arr[pivot] = arr[end];
pivot = end;
while (begin < end && arr[begin] <= key)//找大
{
begin++;
}
arr[pivot] = arr[begin];
pivot = begin;
}
pivot = begin;
arr[pivot] = key;
QuickSort(arr, left, pivot - 1);//递归左
QuickSort(arr, pivot + 1, right);//递归右
}
2) 前后指针法
让prev指向第一个数据,cur指向第二个数据,再找一个关键字key,然后让cur向前遍历数组数据,找到比key小的数据,让prev往前走一步,接着让prev和cur交换,这样就可以保证把小的数据换到前面,大的数据换到后面,当cur找到最后一个数据,最后把key放到prev处。这样排序完一趟也可以具体确定一个数据的确切位置,然后就可以认为,数组中数据被prev分为左右区间,接着去递归左右区间。也是同样的道理,直到递归到剩一个数据就认为有序,接着往后返回,直到所有数据有序。
可以参考图片进行理解
代码实现: (采用三数取中的方法)
void QuickSort1(int* arr, int left, int right)
{
if (left >= right)//剩一个数据认为有序,返回
{
return;
}
int index = GetMidIndex(arr, left, right);//三位取中,防止最坏情况
swep(&arr[left], &arr[index]);//将取到的数放到最前面
int prev = left;//后驱指针
int cur = left + 1;//前驱指针
int key = arr[left];//关键字
while (cur <= right)
{
if (arr[cur] <= key)//cur找小
{
prev++;
swep(&arr[prev], &arr[cur]);
}
cur++;
}
swep(&arr[left], &arr[prev]);
QuickSort1(arr, left, prev - 1);//递归左区间
QuickSort1(arr, prev + 1, right);//递归右区间
}
3)左右指针法
先定义一个左指针begin,和右指针end,再选一个关键字key,让左指针向后遍历,找比key大的数据,让右指针向前遍历,找比key小的数据,两个指针都找到后,然后交换数据。这样也可以把小的数据换到前面,把大的数据换到后面,当begin和end相遇后,把key放到begin指针处。当这样排完一趟后也可以确定一个确切位置的数据,同时也就可以认为数组数据被begin分为左右两个区间,然后去递归左右区间,直到剩一个数据认为有序,然后返回,一次可以确定一个确切一个数据,直到整个递归到整个数组有序。
可以参考图片进行理解:
代码实现: (采用三数取中的方法)
void QuickSort1(int* arr, int left, int right)
{
if (left >= right)
{
return;
}
int index = GetMidIndex(arr, left, right);//三位取中,防止最坏情况
swep(&arr[left], &arr[index]);//将取到的数放到最前面
int prev = left;//后驱指针
int cur = left + 1;//前驱指针
int key = arr[left];//关键字
while (cur <= right)
{
if (arr[cur] <= key)//cur找小
{
prev++;
swep(&arr[prev], &arr[cur]);
}
cur++;
}
swep(&arr[left], &arr[prev]);
QuickSort1(arr, left, prev - 1);//递归左区间
QuickSort1(arr, prev + 1, right);//递归右区间
}
归并排序:
先把数组数据从中间分为左区间begin1到end1,和右区间begin2到end2.前提是左右区间都有序,然后开辟一块同样大小的数组,左右区间同时进行归并,从begin1和begin2开始比,把小的数据往新开辟的数组里放,这样新数组里的数据都是有序的。两个区间总有一个先结束,然后把剩下的数组里的数据拷入新数组,即新数组里的数据全部都有序。最后把新数组里的数据拷入原数组。但是要保证左右区间里的数据都有序,就可以考虑用递归的方法,递归左右区间,直到左右区间剩一个数据,即认为它是有序的。然后往上返回,一步步的递归到原始左右区间都是有序的,然后在进行最终的归并,即整个数组数据都是有序的。
可以参考图片理解:
代码实现:
void _MergeSort(int* arr, int left, int right, int* tmp)
{
if (left >= right)//生一个值认为有序,返回
{
return;
}
int mid = (left + right) >> 1;//找到中间区间
_MergeSort(arr, left, mid, tmp);//递归左区间
_MergeSort(arr, mid + 1, right, tmp);//递归右区间
int begin1 = left;
int end1 = mid;
int begin2 = mid + 1;
int end2 = right;
int index = left;//控制新数组的指针
while (begin1 <= end1 && begin2 <= end2)//左右区间同时进行
{
if (arr[begin1] < arr[begin2])//找小的数据往新数组里放
{
tmp[index++] = arr[begin1++];
}
else
{
tmp[index++] = arr[begin2++];
}
}
//总有一个数组先结束,把没结束数据数据拷入新数组
while (begin1 <= end1)
{
tmp[index++] = arr[begin1++];
}
while (begin2 <= end2)
{
tmp[index++] = arr[begin2++];
}
//将新数组数据拷回原数组
int i = 0;
for (i = 0; i <= right; i++)
{
arr[i] = tmp[i];
}
}
void MergeSort(int* arr, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
return;
}
_MergeSort(arr, 0, n - 1, tmp);//防止malloc重复开辟,在内部进行递归
free(tmp);
}
.
选择排序:
选择排序是先选大的数据往后面放,在选次大的数据往后放,依次循环,直到整个数组数据全部遍历完。这里采用了一个优化的版本,先定义一个左指针begin,和右指针end,然后遍历数组,找最小的数据和最大的数据,找到后小数据放最前面begin处,大数据放最后面end处,依次循环找次大和次小的数据,直到左右指针相遇结束。这样就可以把小数据往前放,大数据往后放,即最后整个数组数都是有序的。
可参考图片进行理解:
代码实现:
void SelectSort2(int* arr, int n)
{
int begin = 0;//前指针
int end = n - 1;//后指针
while (begin < end)
{
int max = begin;
int min = begin;
int i = 0;
for (i = begin; i <= end ; i++)
{
if (arr[i] > arr[max])//找大
{
max = i;
}
if (arr[i] < arr[min])//找大
{
min = i;
}
}
swep(&arr[begin], &arr[min]);//小的换前面
swep(&arr[end], &arr[max]);//大的换后面
begin++;
end--;
}
}
计数排序:
先把一组数据的数据进行遍历,然后开辟一块同样大小的数组,数据全部初始化为0。原数组的数据是几就给新开辟数组对应下标的值进行加1,即绝对映射。这样就可以进行计数。因为数据值可能是比较大的一段区间。为了节省空间,考虑用相对映射的方式,即每个数据都减去一个最小的数据,然后在对应相应的数组下标值进行加1计数。最后再把记的数据的下标从左到右依次相对应的个数,还原到原数组。
可参考图片进行理解:
代码实现:
void CountSort(int* arr, int n)
{
int max = arr[0];
int min = arr[0];
int i = 0;
for (i = 0; i < n; i++)
{
if (arr[i] > max)//找最大的数据
{
max = arr[i];
}
if (arr[i] < min)//找最小的数据
{
min = arr[i];
}
}
int range = max - min + 1;//确定区间,方便相对映射
int* count = (int*)malloc(sizeof(int) * range);
if (count == NULL)
{
return;
}
memset(count, 0, sizeof(int) * range);//初始化开辟数组数据为0
//统计每个数据的次数
int j = 0;
for (j = 0; j < n; j++)
{
count[arr[j] - min]++;//相对映射位置
}
//按次数还原回去,数据还原的是下标
i = 0;
int k = 0;
for (k = 0; k < range; k++)
{
while (count[k]--)
{
arr[i++] = k + min;
}
}
free(count);
}
小结:
八大排序到此完结,提示一下:我们一定要把握好自己的思路。还有一个基数排序,这个不是很实用,后期再写。希望老铁们有所收获。