目录
排序的概念
排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。 它分为内部排序和外部排序.
内部排序:数据元素全部放在内存中的排序。
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。
1.1直接插入排序
把待排序的记录按其关键值的大小逐个插入到一个已经排序好的有序序列中,直到所有的记录插入完为止得到一个新的有序序列.
void InsertSort(int ar[], int left, int right){
for (int i = left + 1; i < right; ++i) {
int k = left;
while (ar[i] > ar[k])
k++;
int tmp = ar[i];
for (int j = i; j > k; --j)
ar[j] = ar[j-1];
ar[k] = tmp;
}
}
总结:元素集合越接近有序,直接插入排序的算法时间效率越高,时间复杂度:O(N^2);空间复杂度O(1),它是一种稳定的排序算法.
1.2希尔排序(缩小增量法)
先选定一个整数n,把待排序文件中所有记录分成个n
组,所有距离为n的记录分在同一组内,并对每一组内的记录进行排序。然后缩小增量n,重复上述分组和排序的工
作。当n=
1
时,所有记录在统一组内排好序。
void ShellSort(int num[], int len)
{
int i, j, temp, step;
for (step = len / 2; step >= 1; step /= 2)//步长间隔每次减半
for (i = step; i < len; i += step)//按步长遍历数组
{
//插入排序过程
if (num[i] < num[i - step])
{
temp = num[i];
for (j = i - step; num[j] > temp; j -= step)
num[j + step] = num[j];
num[j + step] = temp;
}
}
}
希尔排序是对直接插入排序的优化,希尔排序的时间复杂度不好计算,因为增量的取值方法很多,导致很难去计算它的时间复杂度.而且希尔排序也不稳定.
1.3选择排序
每一次从待排的数据元素中选出最大(或者最小)的一个元素,存放在起始位置,直到全部待排序的数据元素排完.
int GetMinIndex(int ar[], int left, int right) {
int min_value = ar[left];
int index = left;
for (int i = left + 1; i < right; ++i) {
if (ar[i] < min_value) {
min_value = ar[i];
index = i;
}
}
return index;
}
void SelectSort(int ar[], int left, int right) {
for (int i = left; i < right - 1; ++i) {
int index = GetMinIndex(ar, i, right);
if (index != i) {
int tmp = ar[i];
ar[i] = ar[index];
ar[index] = tmp;
}
}
}
直接选择排序思考简单易理解但是效率很不好,很少在实际中使用;时间复杂度:O(N^2);空间复杂度O(1);不稳定的排序方法.
1.4堆排序
堆排序
(Heapsort)
是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是
通过堆来进行选择数据。
需要注意的是排升序要建大堆,排降序建小堆。
//堆排序
void _ShiftDown(int ar[], int left, int right, int start) {
int n = right - left;
int i = start;
int j = 2 * i + 1;
int tmp = ar[i];
while (j < n) {
if (j + 1 < n && ar[j] < ar[j + 1])
j++;
if (tmp < ar[j]) {
ar[i] = ar[j];
i = j;
j = 2 * i + 1;
}
else
break;
}
ar[i] = tmp;
}
void HeapSort(int ar[], int left, int right) {
int n = right - left;
int curpos = (n - 1) / 2 + left;
while (curpos >= left) {
_ShiftDown(ar, left, right, curpos);
curpos--;
}
//排序
int end = right - 1;
while (end > left) {
int tmp = ar[end];
ar[end] = ar[left];
ar[left] = tmp;
_ShiftDown(ar, left, end, left);
end--;
}
}
堆排序使用堆来选数,效率变高了.时间复杂度:O(N*logN);空间复杂度:O(1);不稳定的排序方法.
1.5冒泡排序(交换排序)
n个元素需要排序n-1趟,把最大的放在最后面排序结束.
void BubbleSort_1(int ar[], int left, int right)
{
int n = right - left;
for (int i = 0; i < n - 1; ++i)
{
for (int j = 0; j < n - 1 - i; ++j)
{
if (ar[j] > ar[j + 1])
{
Swap(&ar[j], &ar[j + 1]);
}
}
}
}
时间复杂度:O(N^2)
空间复杂度:O(1);
稳定性:稳定.
1.6快速排序
任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止.
//快速排序
int GetMidIndex(int ar[], int left, int right)
{
int low = left, high = right - 1;
int mid = (low + high) / 2;
if (ar[low] < ar[mid] && ar[mid] < ar[high])
return mid;
if (ar[mid] < ar[low] && ar[low] < ar[high])
return low;
return high;
}
int _Partition_3(int ar[], int left, int right)
{
//三者取中
int index = GetMidIndex(ar, left, right);
if (index != left)
Swap(&ar[left], &ar[index]);
int key = ar[left];
int pos = left;
for (int i = left+1 ; i < right; ++i)
{
if (ar[i] < key)
{
pos++;
if (pos != i)
{
Swap(&ar[pos], &ar[i]);
}
}
}
Swap(&ar[left], &ar[pos]);
return pos;
}
void InsertSort_3(int ar[], int left, int right)
{
for (int i = left; i < right; ++i)
{
int j = i;
int tmp = ar[i];
while (j > left && tmp < ar[j - 1])
{
ar[j] = ar[j - 1];
j--;
}
ar[j] = tmp;
}
}
#define M 5
void QuickSort(int ar[], int left, int right)
{
if (left >= right)
return;
if (right - left <= M)
{
InsertSort_3(ar, left, right);
}
else
{
int pos = _Partition_3(ar, left, right); //key
QuickSort(ar, left, pos);
QuickSort(ar, pos + 1, right);
}
}
快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫
快速
排序.
时间复杂度:O(N*logN);
空间复杂度:
O(logN);
稳定性:不稳定.
1.7归并排序
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
核心步骤为:分发和回收.
//归并排序
void _MergeSort(int ar[], int left, int right, int* tmp)
{
//分解
if (left >= right)
return;
int mid = (left + right) / 2;
_MergeSort(ar, left, mid, tmp);
_MergeSort(ar, mid + 1, right, tmp);
//归并
int begin1, end1, begin2, end2;
begin1 = left, end1 = mid; //左数据
begin2 = mid + 1, end2 = right; //右数据
int k = left;
while (begin1 <= end1 && begin2 <= end2)
{
if (ar[begin1] < ar[begin2])
tmp[k++] = ar[begin1++];
else
tmp[k++] = ar[begin2++];
}
while (begin1 <= end1)
tmp[k++] = ar[begin1++];
while (begin2 <= end2)
tmp[k++] = ar[begin2++];
memcpy(ar + left, tmp + left, sizeof(int) * (right - left + 1));
}
void MergeSort(int ar[], int left, int right)
{
int n = right - left;
int* tmp = (int*)malloc(sizeof(int) * n);
_MergeSort(ar, left, right - 1, tmp);
free(tmp);
}
归并的缺点在于需要
O(N)
的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
时间复杂度:
O(N*logN)
空间复杂度:
O(N)
稳定性:稳定
1.8基数排序
根据每一位的数字进行排序
#include"List.h"
#define K 9
#define RADIX 10
List list[RADIX];
// k
//2 7 8
int GetKey(int value, int k)
{
int key;
while (k >= 0)
{
key = value % 10;
value /= 10;
k--;
}
return key;
}
void Distribute(int ar[], int left, int right, int k) //k代表第k次分发
{
for (int i = left; i < right; ++i)
{
int key = GetKey(ar[i], k);
ListPushBack(&list[key], ar[i]);
}
}
void Collect(int ar[])
{
int k = 0;
for (int i = 0; i < RADIX; ++i)
{
ListNode* p = list[i];
while (p != NULL)
{
ar[k++] = p->data;
p = p->next;
}
}
for (int i = 0; i < RADIX; ++i)
ListClear(&list[i]);
}
void RadixSort(int ar[], int left, int right)
{
//初始化基数
for (int i = 0; i < RADIX; ++i)
ListInit(&list[i]);
for (int i = 0; i < K; ++i)
{
//分发
Distribute(ar, left, right, i);
//回收
Collect(ar);
}
}
在数据范围集中时,效率很高;但适用范围很有限;
时间复杂度:O(MAX(N,范围))
空间复杂度;O(范围)
稳定性:稳定;