1、插入排序
思想:每一步将待排序的元素,按照其排序码的大小,插入到前面已经排好序的一组元素的合适位置上面,直到元素全部插完为止。
当插入第i(i>=1)个元素时,前面的arr[1],arr[2]……arr[i-1]都已经是排好序的,此时用arr[i]的排序码和之前的ar[1],arr[2]……arr[i-1]进行比较,放入合适的位置,将原来位置上的元素顺序后移。
元素集合越接近有序,直插排序算法的时间效率就越高。
最优情况下:时间复杂度为:O(n);
最坏情况下:时间复杂度为:O(n^2);
空间复杂度:O(1),是一种稳定的排序算法 。
实现代码如下:
void InsertSort(int arr[], int size)//插入排序
{
int i = 0;
for (i = 1; i < size; i++)//外部排序
{
int key = arr[i];//插入第i个元素时
int j = 0;
for (j = i - 1; j >= 0; j--)//内部排序
{
if (arr[j] <= key)
break;
else
arr[j + 1] = arr[j];//后移
}
arr[j + 1] = key;
}
}
2、选择排序
思想:分为两头,left存放最小的元素,right存放最大的元素,寻找最小最大的元素需要在[left,right]这个闭区间里面寻找,找到之前就存放在相应的位置。然后left++,right–;逐渐缩小查找的范围,直到left>right循环结束。
要特别注意一点:当最大值的下标刚好是left,那么将最小值移到left位置时,将会把最大值换走,所以此时应该保存最大值的下标:max=min;
代码如下:
void SelectSort(int arr[],int size)//选择排序
{
int left = 0;
int right = size - 1;
int min;
int max;
while (left < right)
{
min = left;//注意,一定不能 初始化为0,否则后面的比较会把原先选出来的最值带入比较
max = right;
for (int i = left; i <= right; i++)
{
if (arr[i]>arr[max])//找到最大值
max = i;
if (arr[i] < arr[min])//找到最小值
min = i;
}
swap(&arr[left], &arr[min]);
if (max == left)//判断如果最大值解释最左边元素的情况,因为换最小值的时候就被换走了,所以需要重新赋值
max = min;
swap(&arr[right], &arr[max]);
left++;//缩小范围
right--;
}
}
3、希尔排序
思想:希尔排序是对直接插入排序的优化
在gap的间隔的元素直插排序,目的是为了让数组的元素基本有序,然后再改变gap的大小,再次排序。
代码如下:
void ShellSort(int arr[], int size)//希尔排序
{
int gap = size;
int i = 0;
int j = 0;
int g = 0;
while (gap > 1)
{
gap = gap / 3 + 1;
for (g = 0; g < gap; g++){
for (i = gap + g; i < size; i+=gap)
{
int key = arr[i];
for (j = i - gap; j >= 0; j-=gap)
{
if (arr[j] <= key)
break;
else
arr[j + gap] = arr[j];
}
arr[j + gap] = key;
}
}
}
}
代码优化如下:
void ShellSort1(int arr[], int size)//希尔排序简化版
{
int gap = size;
int i = 0;
int j = 0;
while (gap > 1)
{
gap = gap / 3 + 1;
for (i = gap; i < size; i++)
{
int key = arr[i];
for (j = i - gap; j>=0; j -= gap)
{
if (arr[j] <= key)
break;
else
arr[j + gap] = arr[j];//后移
}
arr[j + gap]=key;
}
}
}
4、冒泡排序
思想 :冒泡排序是我们接触最早的一种排序了,分为两层循环,外层循环是负责控制内层循环的范围,内存循环主要是将最大的元素放在最后面。
有一个优化的地方 很重要,就是设置一个flag,将其初始值设为0,每当进行交换后就将其值置为1,如果循环后发现flag为0,说明后面就已经有序了,循环不需要进行下去了,break退出。
代码如下:
void BubbleSort(int arr[], int size)//冒泡排序
{
int i;
int j;
int flag;//这次冒泡过程中是否有数据交换
for (i = 0; i <= size-1; i++)//代表需要循环的趟数
{
flag = 0;
for (j = 0; j <size - i-1; j++)//每一趟 需要比较的次数
{
if (arr[j] > arr[j + 1])
{
swap(&arr[j+1], &arr[j]);
flag = 1;
}
}
if (flag == 0)//表示没有数据交换了,表示剩下的数据有序
{
return;
}
}
}
5、快速排序
思想:任取待排序序列的某个元素 作为基准值,按照排序码,将序列分为两个部分,左序列的元素均小于基准值,右序列的元素均大于基准值,然后左右子序列复制该过程,直到所有的元素都排列到相应的位置为止。左右切割。
分为3种方法:
1、左右下标法
代码如下:
int SortPart1(int arr[], int left, int right)//快排第1种方法
{
int key = arr[right];
int begin = left;//从前往后找第一个比key大的数,找到交换
int end = right;//从后往前找到第一个比key小的数,找到交换
while (begin < end)
{
while ((begin < end) && arr[begin] <= key)//反例:555555,如果没有=,就需要一直交换,破坏稳定性
{
begin++;
}
while ((begin<end) && arr[end]>=key)
{
end--;
}
if (begin < end)
{
swap(&arr[end], &arr[begin]);
}
}
swap(&arr[begin], &arr[right]);
return begin;
}
void SortFercious(int arr[], int left, int right)
{
if (left >= right)
return;
int div = SortPart1(arr, left, right);
SortFercious(arr, left, div - 1);
SortFercious(arr, div + 1, right);
}
void QuickSort(int arr[], int size)
{
SortFercious(arr, 0,size - 1);
}
2、挖坑法
代码如下:
int SortPart2(int arr[], int left, int right)//挖坑法,快排第二种方法
{
int key = arr[right];
int begin = left;
int end = right;
while (begin < end)
{
while ((begin < end) && arr[begin] < key)
{
begin++;
}
arr[end] = arr[begin];
while ((begin < end) && arr[end] > key)
{
end--;
}
arr[begin] = arr[end];
}
arr[begin] = key;
return begin;
}
3、前后下标法
思想:
代码如下:
int SortPart3(int arr[], int left, int right)//第三种方式,前后下标法
{
int key = arr[right];
int prev = left;
int cur = left + 1;
while (cur <= right)
{
while (prev != cur)
{
if (arr[cur] < arr[prev])
swap(&arr[cur], &arr[prev]);
prev++;
}
cur++;
}
swap(&arr[prev], &arr[right]);
return prev;
}
6、归并排序
思想:是将待排序的元素都看作是有序的,再按照将有序链表的归并起来就和很容易了。
void Merge(int arr[], int left, int mid, int right, int extra[])//归并,假定要归并的两个序列本身就是有序的
{
int i = left;
int il = left;
int ir = mid;
for (; il < mid&&ir <= right; i++)
{
if (arr[il] <= arr[ir])
{
extra[i] = arr[il];
il++;
}
else
{
extra[i] = arr[ir];
ir++;
}
}
while (il < mid)
{
extra[i++] = arr[il++];
}
while (ir <= right)
{
extra[i++] = arr[ir++];
}
for (int i = left; i <= right; i++)
{
arr[i] = extra[i];
}
}
void MergeSortLoop(int array[], int size, int extra[])
{
int gap;
int i;
// 表示需要合并多少层
for (gap = 1; gap < size; gap = 2 * gap) {
// 每层需要合并多少次
for (i = 0; i < size; i += 2 * gap) {
int left = i;
int mid = i + gap;
int right = mid + gap - 1;
if (mid >= size) {
break;
}
if (right >= size) {
right = size - 1;
}
Merge(array, left, mid, right, extra);
}
}
}
void MergeSort(int arr, int size)
{
int *extra = (int *)malloc(sizeof(int)*size);
assert(extra);
//Recursion(arr, 0, size, extra);
MergeSortLoop(arr, size, extra);
free(extra);
}
7、堆排序
堆排序最重要的就是建堆和向下调整,需要排序,所以我们需要建一个大堆
基本思想:
堆排序的基本思想是:将待排序列构成一个 大顶堆,此时,整个序列的最大节点就是堆顶的根节点。将其与末尾元素进行交换,此时的末尾就是一个最大值,然后将剩下的n-1个元素重新构成一个大堆,如此反复执行,便能够得到一个有序序列了。
步骤一:构建 初始堆,将给定的无序序列构成一个大顶堆(一般升序采用大顶堆,降序采用小堆)
步骤二:将栈顶元素和末尾元素进行交换,使得末尾元素最大,然后继续调整堆,将栈顶元素 和末尾元素交换,得到第二大的元素,如此反复的交换重建、交换。
void AjustDownLoop(int arr[], int size, int root)
{
int parent = root;
int left;
int right;
int maxchild;
while (parent<size)
{
left = parent * 2 + 1;
right = parent * 2 + 2;
if (left>=size)//没有右孩子
{
return;
}
maxchild = left;
if ((right<size) && (arr[right] > arr[left]))
{
maxchild = right;
}
if (arr[parent] > arr[maxchild])
{
return;
}
swap(arr + parent, arr + maxchild);
parent = maxchild;
}
}
void HeapSort(int arr[], int size)//堆排序
{
int i = (size - 2) / 2;
for (; i >= 0; i--)
{
AjustDownLoop(arr, size, i);//向下调整
}
for (i = 0; i < size; i++)
{
swap(arr + 0, arr + size - i - 1);//交换 最大的元素和最后一个元素的位置
AjustDownLoop(arr, size - 1 - i, 0);//减小堆得调整范围
}
}