算法稳定性:
通俗理解------A(1)=80;A(2) = 80;
排序前A(1)在A(2)前,排序后A(1)还在A(2)前则稳定,否则不稳定
代码中使用到的几个函数和指针:
typedef int(*Compare)(int left, int right); //函数指针,可灵活选择排序的顺序
int Less(int left, int right)
{
return left < right;
}
int Greater(int left, int right)
{
return left > right;
}
void Swap(int *left, int* right)
{
int tmp = *left;
*left = *right;
*right = tmp;
}
冒泡排序:多在数据规模较小的时候使用 时间复杂度O(n^2) 空间复杂度O(1) 稳定排序
思想:
每次循环比较相邻的两个元素的大小,将较大(小)的往后冒,每一趟下来最后一个元素都是最大的,每排一趟,最后的元素下标向前移一位,最终完成排序。
void BubbleSort(int array[], int size, Compare com)
//两两进行比较,将大的放置到后面,第一趟下来最后的值就是最大的值,依次类推,完成排序
{
int i = 0;
int j = 0;
int flag = 0; //设置标记,优化冒泡
for (; i < size - 1; i++) //最后一个元素不需要冒泡
{
int flag = 0;
for (j = 0; j < size - i - 1; j++)
{
if (com(array[j], array[j + 1]))
{
Swap(&array[j], &array[j + 1]);
flag = 1;
}
}
if (flag != 1)
{
break;
}
}
}
选择排序://元素重复较多时间复杂度O(n^2) 空间复杂度O(1) 不稳定
思想:
内层循环找到最大的元素,标记下标,外层循环控制将最大的元素与最后一个位置的元素进行交换,直到有序
void SelectSort(int array[], int size, Compare com) //初级版本
{
//先默认的把首位置下标为最小值下标。然后遍历后面的数字,出现比这个值还小的数
//就把那个数的下标赋给最小值下标。
//循环遍历直到有序
int i = 0;
int j = 0;
int min = 0;
for (i = 0; i < size - 1; i++)
{
min = i; //默认首元素最小
for (j = i + 1; j < size; j++)
{
if (com(array[min], array[j])) //如果有数比m下标中的数还小,就把这个数的下标放到min中
{
min = j;
}
}
if (min != i) //若是同一位置,不需要交换
Swap(&array[i], &array[min]);
}
}
void SelectSort3(int array[], int size) //优化版
{
int i = 0;
int maxPos = 0; //标记最大的
int minPos = 0; //标记最小的
int begin = 0;
int end = size - 1;
while (begin < end)
{
i = begin;
minPos = begin;
maxPos = begin;
for (; i <= end; i++) //遍历分别找到最大的和最小的下标
{
if (array[i] < array[minPos])
minPos = i;
if (array[i] > array[maxPos])
maxPos = i;
}
if (minPos != begin) //最小的不在最开始的位置
{
Swap(&array[minPos], &array[begin]);
}
if (maxPos == begin)
{
maxPos = minPos;
}
if (maxPos != end) //最大的不在最后的位置
{
Swap(&array[maxPos], &array[end]);
}
begin++;
end--;
}
}
插入排序:// 数据元素接近有序、数据量少时间复杂度O(n^2)空间复杂度O(1)稳定
思想:
默认第1个元素前的元素都已经有序,将之后的元素全部插入到前面
插入方法:
标记待插数,与前面的元素比较,找到插入的位置,将要插入位置的元素全向后搬移,腾出位置插入待插数,依次循环直到有序
void InsertionSort(int array[], int size, Compare com) //普通版
{
int i = 0;
int end = 0;
int tmp = 0;
for (i = 1; i < size; i++)
{
tmp = array[i]; //待测数
end = i - 1; //待测数的前一个数即是有序数的最后一个数
while(end >= 0 && com(array[end], tmp)) //找到要插入的位置
{
array[end + 1] = array[end]; //不是要插入的位置,元素向后搬移
end--;
}
array[end + 1] = tmp; //插入
}
}
void BinInsSort(int array[], int size) //优化版,二分查找法
{
int i = 0;
int j = 0;
int tmp = 0;
int low = 0;
int high = 0;
int mid = 0;
// 1 2 3 4 5 6 7 8 9 0
for (i = 1; i < size; i++)
{
tmp = array[i];
low = 0;
high = i - 1;
while (low <= high) // 当up移动到low左侧时,结束循环。注意,此处一定要带有等号,否则排序会失败(第一次排序可验证)
{
mid = low + (high - low)/2; //取中
if (tmp < array[mid]) //当待插入元素小于中间位置的元素时
{
high = mid - 1; //待插入元素应该插在在前半个表中
}
else
{
low = mid + 1;
}
}
for (j = i - 1; j >= low; j--) //从要插入元素的位置开始将元素依次搬移
{
array[j + 1] = array[j];
}
array[low] = tmp; //插入
}
}
希尔排序://数据元素无序、数据量大时间复杂度O(N^1.3)空间复杂度O(1)不稳定
思想:
分组进行插入排序
先定义一个步长序列,依次递减至一,然后按照步长将数据分组,所有距离为步长倍数的数分为一组,在组内对数据进行插入排序,步长序列递减。
void ShellSort(int array[], int size)
{
int end = 0;
int tmp = 0;
int gap = size;
//生成步长序列
while(gap > 1) //gap > 1
{
gap = gap / 3 + 1; //调整步长序列
int i = gap;
//插入排序
for (; i < size; i++) //i一次走一步直至所有元素都排好序
{
tmp = array[i]; //待测数
end = i - gap; //有序数列的最后一个元素
while (end >= 0 && array[end] > tmp) //找到要插入的位置
{
array[end + gap] = array[end]; //
end -= gap;
}
array[end + gap] = tmp; //插入
}
}
}
堆排序://数据规模较大时
思想:
先将所有元素构成大根堆将堆顶元素和堆最后一个元素交换,交换后重新调整堆为大根堆依次类推直到有序。
函数分为调整部分和排序部分。
void AdjustHeap(int array[], int size, int root, Compare com)//小堆----->向下调整
{
int child = root * 2 + 1; //左孩子
while (child < size)
{
if (child + 1 < size && com(array[child + 1], array[child]))//找到左右孩子中小的,标记为child
{
child += 1;
}
if (com(array[child], array[root])) //判断是否需要交换位置
{
Swap(&array[root], &array[child]);
root = child;
child = root * 2 + 1;
}
else
{
break;
}
}
}
void HeapSort(int array[], int size, Compare com)
{
int end = size - 1; //标记最后一个结点的下标
int LastRoot = (size - 2) >> 1; //最后一个非叶子节点
while (LastRoot >= 0) //建堆
{
AdjustHeap(array, size, LastRoot, com);
LastRoot--;
}
while (end > 0) //最后一个节点不交换 //堆的删除
{
Swap(&array[0], &array[end]); //先交换
AdjustHeap(array, end, 0, com); //在调整(从end的前一个节点开始调)
end--; //在向前移
}
}
快速排序:数据接近有序时快速排序算法不适用,效率低
标记一个关键码,将比关键码小的元素,向前搬移,将比关键码大的元素向后搬移。
优化:
1. 三值取中确定基准值 (返回中间值的索引)
取第一个元素,最后一个元素,中间的元素判断大小
2. 当区间比较小时,就可以使用插入排序,直接对这个区间进行排序从而有效减少递归次数
Right - left < 16 -----------------插入排序
int partion(int array[], int left, int right)//双指针相向移动
{
int key = array[right - 1];
int begin = left;
int end = right - 1;
while (begin < end)
{
while (begin < end && array[begin] <= key)//从左往右找最大的//begin<end防止在查找的时候begin越界
begin++;
while (begin < end && array[end] >= key) //从右往左找最小的
end--;
//找到一个最大的和最小的
if (begin < end) //不在同一位置
{
Swap(&array[begin], &array[end]);
}
}
//begin 和 end 走到同一位置 ,若不是最后位置交换
if (begin != right - 1)
{
Swap(&array[begin], &array[right - 1]);
}
return begin;
}
int partion2(int array[], int left, int right) //挖坑法
{
int key = array[right - 1];
int begin = left;
int end = right - 1;
while (begin < end)
{
while (begin < end && array[begin] <= key)//从左往右找最大的//begin<end防止在查找的时候begin越界
begin++;
if (begin < end) //填坑
array[end] = array[begin];
while (begin < end && array[end] >= key) //从右往左找最小的
end--;
if (begin < end)
array[begin] = array[end];
}
//begin, end到同一位置
array[end] = key; //填最后一个坑
return begin;
}
int partion3(int array[], int left, int right)//双支指针前移法
{
int key = array[right - 1];
int begin = left - 1;//此处不会发生越界问题,并未访问数组-1处的元素
int end = left;
//定义两个指针一个begin和end,从左往右遍历,end的值小于基准值时让begin前进一个位置
//并检测是否与end为同一位置,若不是则交换位置上的值,end就是检测所有数据中比基准值小的值
//利用begin将其向前挪,循环结束时需交换begin位置的元素和基准值
while(end < right)//end指向当前元素,每次循环都要往前走,直到遍历完序列
{
if (array[end] < key)
{
//end处的元素小于key,begin前移,并判断
begin++;
if (begin != end)
{
Swap(&array[begin], &array[end]);
}
}
end++;
}
if (array[++begin] != key) //此时begin的值一定小于key,begin的下一个元素大于等于key
//交换begin+1 和最后的元素
Swap(&array[begin], &array[right - 1]);
return begin ;
}
void QuickSort(int array[], int left, int right) //递归版本
{
int div = 0;
if (left < right)
{
div = partion3(array, left, right);
QuickSort(array, left, div);
QuickSort(array, div + 1, right);
}
}
void QickSortByLoop(int array[], int size) //非递归版本,借助栈
{
Stack s;
int left = 0;
int mid = 0;
int right = size;
assert(array);
if (size <= 1)
{
return;
}
//用栈保存每一个待排序子串的首尾元素下标,
//下一次while循环时取出这个范围,对这段子序列进行partion操作
InitStack(&s);
PushStack(&s, left); //左入栈
PushStack(&s, right); //右入栈
while (!StackEmpty(&s))//栈不为空
{
right = TopStack(&s); //右出栈
PopStack(&s);
left = TopStack(&s); //左出栈
PopStack(&s);
if (left < right)
{
mid = partion3(array, left, right);
PushStack(&s, left);
PushStack(&s, mid);
PushStack(&s, mid + 1);
PushStack(&s, right);
}
}
}
归并排序: 外部排序,适用于数据量大的情况
假设数组A有N个元素,那么可以看成数组A是又N个有序的子序列组成,每个子序列的长度为1,然后再两两合并,得到了一个 N/2 个长度为2或1的有序子序列,再两两合并,如此重复,值得得到一个长度为N的有序数据序列为止。
合并算法:即合并两个有序的子序列,设置两个指针,p1,p2,将p1,p2所指向元素当中值较小的不断复制到临时空间,最后把没复制完的子序列的剩余yuan复制到临时空间
void _MergeData(int* array, int left, int mid, int right, int* tmp) //左闭右开区间
{
int begin1 = left;
int end1 = mid;
int begin2 = mid;
int end2 = right;
int index = left;
//两个分组中都有元素时
while (begin1 < end1 && begin2 < end2)
{
if (array[begin1] <= array[begin2]) //将两部分元素中较小的赋值到临时空间中
{
tmp[index++] = array[begin1++];
}
else
{
tmp[index++] = array[begin2++];
}
}
while (begin1 < end1) //将剩余元素赋值到临时空间中
tmp[index++] = array[begin1++];
while (begin2 < end2)
tmp[index++] = array[begin2++];
}
void _MergeSort(int* array, int left, int right, int* tmp)
{
//当前分组中只有一个元素时归并结束
if (right - left > 1) //递归出口
{
int mid = left + ((right - left) >> 1); //取中间值
_MergeSort(array, left, mid, tmp); //拆分
_MergeSort(array, mid, right, tmp);
_MergeData(array, left, mid, right, tmp); //归并
memcpy(array + left, tmp + left, sizeof(array[0])*(right - left));//将当前tmp数组中的元素拷贝回array
}
}
void MergeSort(int array[], int size)
{
int *tmp = (int*)malloc(sizeof(array[0])*size);
if (NULL == tmp)
{
return;
}
if (size <= 1)
{
return;
}
_MergeSort(array, 0, size, tmp);
free(tmp);
}
void MergeSortNor(int array[], int size)
{
//设置步长从1开始,每次递增二倍
int gap = 1;
int i = 0;
int left = 0;
int right = size;
int* tmp = (int*)malloc(sizeof(array[0])*size);
if (NULL == tmp)
{
return;
}
while (gap < size)
{
int mid = left + ((right - left) >> 1);
for (i = 0; i < size; i += 2 * gap)
{
left = i;
mid = left + gap;
if (mid > size) //mid越界
mid = size;
right = mid + gap;
if (right > size) //right越界
right = size;
_MergeData(array, left, mid, right, tmp); //数据排序
}
memcpy(array, tmp, sizeof(array[0])*size);
gap *= 2;
}
free(tmp);
}
计数排序:适用于数据量大,但是大小比较集中的数据排序。
1.确定数据大小
2.分配适当空间
3.统计数据元素出现次数,将结果保存至临时空间
4.数据回收----将tmp中的数据反向放回array数组中
void CountSort(int array[], int size)
{
int i = 0;
int MaxValue = array[0];
int MinValue = array[0];
int TmpSize = 0;
int Index = 0;
//确定大小
for (i = 0; i < size; i++)
{
if (array[i] < MinValue)
{
MinValue = array[i];
}
if (array[i] > MaxValue)
{
MaxValue = array[i];
}
}
//分配空间
TmpSize = MaxValue - MinValue + 1;
int* tmp = (int*)malloc(sizeof(int)* TmpSize);
if (NULL == tmp)
{
return;
}
memset(tmp, 0x00, sizeof(int)*TmpSize);
//统计每个数据个数,并将其个数放置进tmp的指定位置
for (i = 0; i < size; i++)
{
tmp[array[i] - MinValue]++;
}
//数据回收---将tmp数组的数据重新放回array
i = 0;
while (i < TmpSize)
{
while (tmp[i]--)
{
array[Index++] = i + MinValue;
}
i++;
}
free(tmp);
}
基数排序:
LSD:低关键码优先----从数字的低位到高位依次排序
MSD:高关键码优先---从高高位到低位依次排序
LSD:
得到数组的最大的数的位数,从个位开始,将数据依次放入指定的桶中,再将桶中的元素拷贝回原数组,在次对原数组中的数的高一位进行处理。
统计每个桶中有效元素个数
计算每个桶的起始地址
将元素按照当前位置放入桶中
对元素进行回收--------将桶中元素拷回原数组
将桶中的数据拷贝回原数组,则会转化为排序下列数据:321, 255, 655, 262, 374, 281, 987, 789, 198。再对该组数据按百位进行排序。
最后形成有序数据: 198,255, 262, 281, 321, 374, 655 , 789, 987.
代码:
void _RadixSortLSD(int array[], int size, int* bucket) //M位 个数N
{
int i = 0;
int bitIdx = 0;
int bitIdxNum = 0;
int radix = 1;
bitIdxNum = GetbitNum(array, size);
for (bitIdx = 0; bitIdx < bitIdxNum; bitIdx++)
{
int count[10] = { 0 }; //有效元素个数
int addrStart[10] = { 0 }; //起始地址
//统计每个桶中元素的个数
for (i = 0; i < size; i++)
{
count[array[i] / radix % 10]++;
}
//计算每个桶的起始地址
for (i = 1; i < 10; i++)
{
addrStart[i] = addrStart[i - 1] + count[i - 1];
}
//将元素按照当前位置放入对应的桶中
for (i = 0; i < size; i++)
{
int bucketNo = array[i]/ radix % 10; //桶号
bucket[addrStart[bucketNo]++] = array[i];//先利用桶号计算出起始地址,将元素放入该起始地址处
//然后当前桶的起始地址+1
}
//对元素进行回收
memcpy(array, bucket, sizeof(array[0])* size);
radix *= 10;
}
}
void RadixSort(int array[], int size)
{
if (size < 2)
{
printf("数据量小于2,无需排序!\n");
return;
}
int* bucket = (int*)malloc(sizeof(array[0])*size);
if (NULL == bucket)
{
return;
}
_RadixSortLSD(array, size, bucket);
free(bucket);
}
MSD: 此处需借助递归思想,先对将数据按最高位的大小放入指定的桶中,然后对每个桶的低位递归的进行置桶和拷贝操作。
代码:
void _RadixSortMSD(int array[], int left, int right, int* bucket, int bitNum)//高关键码优先,采用递归的方式
{
int i = 0;
int radix = pow(10, bitNum);
int count[10] = { 0 }; //有效元素个数
int addrStart[10] = { 0 }; //起始地址
if (bitNum < 0)
{
return;
}
//统计每个桶中元素的个数
for (i = left; i < right; i++)
{
count[array[i] / radix % 10]++;
}
//计算每个桶的起始地址
for (i = 1; i < 10; i++)
{
addrStart[i] = addrStart[i - 1] + count[i - 1];
}
//将元素按照当前位置放入对应的桶中
for (i = left; i < right; i++)
{
int bucketNo = array[i] / radix % 10; //桶号
bucket[addrStart[bucketNo]++] = array[i];//先利用桶号计算出起始地址,将元素放入该起始地址处
//然后当前桶的起始地址+1
}
//对元素进行回收
memcpy(array+ left, bucket, sizeof(array[0])* (right - left)); //拷贝时拷贝到array的对应位置
for (i = 0; i < 10; i++)//排每个桶元素的低一位
{
int begin = addrStart[i] - count[i];//当前桶的起始地址 = 当前桶的地址 - 当前桶的元素个数
int end = addrStart[i];
if (begin + 1 >= end) //当前桶的元素只有一个或小于1排序下一个桶
{
continue;
}
_RadixSortMSD(array, begin, end, bucket, bitNum--);
}
}
void RadixSort(int array[], int size)
{
if (size < 2)
{
printf("数据量小于2,无需排序!\n");
return;
}
int* bucket = (int*)malloc(sizeof(array[0])*size);
if (NULL == bucket)
{
return;
}
_RadixSortMSD(array, 0, size, bucket, GetbitNum(array, size) - 1);
free(bucket);
}