1. 冒泡排序
基本思想:它重复地走访要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。直到没有再需要交换,也就是说该数列已经排序完成。
假设有一个大小为 N 的无序序列。以升序冒泡排序为例,冒泡排序就是要每趟排序过程中通过两两比较相邻元素,将小的数字放到前面,大的数字放在后面。
空间复杂度:O(N)
时间复杂度:O(N^2)
稳定排序
void Swap(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
return;
}
void Print(int array[], int size)
{
int i = 0;
for (; i < size; i++)
{
printf("%d ", array[i]);
}
printf("\n");
}
void BubbleSort(int array[], int size)
{
if (size <= 1)
return;
int bound = 0;
for (; bound < size; ++bound)
{
int i = size-1;
for (; i >bound; --i)
{
if (array[i] < array[i-1])
{
Swap(&array[i], &array[i-1]);
}
}
}
Print(array, size);
return;
}
2. 选择排序
基本思想:相当于打擂台,将初始时在序列中找到最小元素,放到擂台上;然后,再将剩余未排序元素与擂台上数字比较,若比擂台上数字小,就交换,然后将擂台上的数字作为有序序列放在开头,以此类推,直到所有元素均排序完毕。
空间复杂度:O(N)
时间复杂度:O(N^2)
不稳定排序
//打擂台 array[bound](最小值)放在擂台上,若比擂台上数小,交换。
void SelectSort(int array[], int size)
{
if (size <= 1)
return;
int bound = 0;
for (; bound < size; ++bound)
{
int i = bound;
for (; i < size; i++)
{
if (array[i] < array[bound])
{
Swap(&array[i], &array[bound]);
}
}
}
Print(array, size);
return;
}
3. 插入排序
基本思想:每一步将一个待排序的元素,按其排序码的大小,插入到前面已经排序好的元素的合适位置上去,直到元素全部插完为止。
算法过程:
- 从第一个元素开始,该元素可以认为已经被排序
- 取出下一个元素,在已经排序的元素序列中从后向前扫描
- 如果该元素(已排序)大于新元素,将该元素移到下一位置
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
- 将新元素插入到该位置后
- 重复步骤2~5
空间复杂度:O(1)
时间复杂度:O(N^2)
稳定排序
如果数组中有序数较多或者数组的长度较短,则插入排序的效率更高。
void InsertSort(int array[], int size)
{
if (size <= 1)
return;
int bound = 1;//假设第一个元素就是一个有序的线性表
for (; bound < size; ++bound)
{
int bound_val = array[bound];
int i = bound;
for (; i>0; --i)
{
if (array[i - 1]>bound_val)
{
array[i] = array[i - 1];
}
else
{
break;
}
}
array[i] = bound_val;
}
Print(array, size);
return;
}
4. 希尔排序——改进的插入排序
基本思想:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序。
如图所示:
希尔序列最优序列时间复杂度为:O(N^1.3)
空间复杂度:O(1);
不稳定排序
void ShellSort(int array[], int size)
{
Print(array, size);
if (size <= 1)
return;
//用希尔序列n/2,n/4,n/8....1
int gap = size / 2;
for (; gap >= 1; gap /= 2)//生成希尔序列
{
//插入排序
int bound = gap;
for (; bound < size; bound++)
{
int bound_val = array[bound];
int i = bound;
for (; i >= gap; i -= gap)
{
if (array[i - gap]>bound_val)
{
array[i] = array[i - gap];
}
else
{
break;
}
}
array[i] = bound_val;
}
}
Print(array, size);
return;
}
5. 堆排序
基本思想:指利用堆这种数据结构所设计的一种排序算法。
升序—建大堆,降序—-建小堆
执行如下步骤:
- 把堆顶元素array【0】和当前大堆的最后一个元素交换;
- 堆元素个数减1;
- 向下调整根节点
重复该过程。
时间复杂度:O(NlogN);
空间复杂度:O(1);
不稳定排序
void AdjustDown(int array[], int size, int pRoot)
{
while (1)
{
int left = 2 * pRoot + 1;
int right = 2 * pRoot + 2;
int max;
if (left >= size)
return;
max = left;
if (right<size&&array[right]>array[left])
{
max = right;
}
if (array[pRoot] < array[max])
{
Swap(&array[pRoot], &array[max]);
}
pRoot = max;
}
return;
}
void HeapCreate(int array[], int size)
{
int i = (size-1-1)/2;
for (; i >=0; i--)
{
AdjustDown(array, size, i);
}
}
void HeapSort(int array[], int size)
{
if (size <= 1)
return;
HeapCreate(array, size);//建堆
int i = 0;
for (; i < size; i++)
{
Swap(&(array[0]), &(array[size - 1 - i]));//每次都把最后一个结点的树与根节点交换,即把最大的数都放在了最后
AdjustDown(array, size - 1 - i, 0);
}
Print(array, size);
return;
}
6. 归并排序
基本思想:将待排序的元素序列分成两个长度相等的子序列,对每一个子序列排序,然后将他们合并成一个序列。
如图:
时间复杂度: O(NlogN)
空间复杂度: 对于数组来说, O(N)
稳定性: 稳定排序
void MergeArray(int array[], int begin, int mid, int end, int *tmp)//实现归并的功能
{
int cur1 = begin;
int cur2 = mid;
int output = begin;
while (cur1 < mid&&cur2 < end)
{
if (array[cur1] <array[cur2])
{
tmp[output++] = array[cur1++];
}
else
{
tmp[output++] = array[cur2++];
}
}
//有的数组没有走完,把该数组剩下的元素加到tmp后边
while (cur1 < mid)
{
tmp[output++] = array[cur1++];
}
while (cur2 < end)
{
tmp[output++] = array[cur2++];
}
//把缓存区的数拷贝到array中
memcpy(array+begin, tmp+begin, sizeof(int)*(end - begin));
}
//非递归实现
void MergeSortLoop(int array[], int size)
{
if (size <= 1)
return;
int *tmp = (int *)malloc(sizeof(int)*size);
int gap = 1;
for (; gap <= size / 2; gap *= 2)
{
int i = 0;
for (; i < size; i += 2 * gap)
{
int begin = i;
int mid = i+gap;
if (mid >= size)
mid=size;
int end = i+2 * gap;
if (end >= size)
end=size;
MergeArray(array, begin, mid, end, tmp);
}
}
free(tmp);
}
//递归实现
void _MergeSort(int array[], int begin, int end, int *tmp)//该函数完成递归
{
if ((end - begin) <= 1)
return;
int mid = begin + (end -begin) / 2;
_MergeSort(array, begin, mid, tmp);
_MergeSort(array, mid, end, tmp);
MergeArray(array, begin, mid, end, tmp);
}
void MergeSort(int array[], int size)
{
int *tmp = (int *)malloc(sizeof(int)*size);//申请一个缓冲区
_MergeSort(array, 0, size, tmp);
free(tmp);
return;
}
7. 快速排序
基本思想:任取待排序元素序列中的某个元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
快速排序使用分治策略来把一个序列分为两个子序列。步骤为:
- 从序列中挑出一个元素,作为”基准”(pivot).
- 把所有比基准值小的元素放在基准前面,所有比基准值大的元素放在基准的后面(相同的数可以到任一边),这个称为分区(partition)操作。
对每个分区递归地进行步骤1~2,递归的结束条件是序列的大小是0或1,这时整体已经被排好序了。
如图:
时间复杂度:最坏情况下为O(N^2),平均为O(NlogN)
空间复杂度:O(N)
不稳定排序
int Partion(int array[], int begin, int end)
{
int left = begin;
int right = end-1;
int tmp = array[end - 1];//基准值
while (left < right)
{
while (left < right&&array[left] <= tmp)//让left从左往右找第一个大于基准值的数
{
++left;//没找到,left往后+1;
}
while (left < right&&array[right] >= tmp)//让right从右往左找第一个小于基准值的数
{
--right;//没找到,right-1;
}
//这两个循环结束后,表示left 就指向了第一个大于 基准值 的元素,right 就指向了第一个小于 基准值 的元素
if (left < right)
{
Swap(&array[left], &array[right]);
}
}
//当left和right重合时,将其与基准值交换
Swap(&array[left], &array[end - 1]);
return left;
}
void _QuickSort(int array[], int begin, int end)
{
if ((end - begin) <= 1)
return;
int mid = Partion(array, begin, end);//Partion完成基准值选择 和交换,返回基准值的位置
_QuickSort(array, begin, mid);
_QuickSort(array, mid + 1, end);
}
void QuickSort(int array[], int size)
{
_QuickSort(array, 0, size);
}
重点:
如何证明 left 和 right 重合之后, left 指向的元素一定大于等于基准值呢?
情况有两种:
1. 由于 ++left, 导致 left 和 right 重合
此时就看 right 之前指向的元素是否是大于等于基准值的元素
结合第二个循环和swap操作, right也指向了一个大于基准值的元素
2. 由于 –right, 导致 right 和 left 重合
此时就看 left 之前指向的元素是否是大于等于基准值的元素
结合第一个循环, 确实 left 指向了一个大于等于基准值的元素