1.冒泡排序
时间复杂度:平均:O(N^2),最坏:O(N^2),最好:O(N)
思想:从后往前开始,相邻的两两进行比较,将小的逐渐移到前面。(也可以从前往后开始两两相邻的进行比较)
代码:
void BubbleSort(int *a, int N)
{
if (a == NULL || N <= 0)
return ;
int i, j;
for (i = 0; i < N; i++)
{
for (j = N-1; j > i; j--)
{
if (a[j - 1] > a[j])
swap(&a[j - 1], &a[j]);
}
}
}
冒泡排序的优化:若一趟下来没有数据交换,则说明此时数组已经有序,此时算法可以结束。而原来的冒泡排序还是一遍遍比较,直到循环结束。
代码:
void BubbleSort2(int *a, int N)
{
if (a == NULL || N <= 0)
return ;
int i, j;
bool flag = true;
for (i = 0; i < N && flag; i++)
{
flag = false;
for (j = N-1; j > i; j--)
{
if (a[j - 1] > a[j])
{
swap(&a[j - 1], &a[j]);
flag = true;
}
}
}
}
2.选择排序
时间复杂度:平均:O(N^2),最坏:O(N^2),最好:O(N^2)思想:每次从右边中的数选一个最小的和左边当前位置的进行较换
分析:最大特点就是交换移动数据的次数相当少,这样也就节约了相应的时间。性能上略优于冒泡排序。
代码:
void SelectSort(int *a, int N)
{
if (a == NULL || N <= 0)
return ;
for (int i = 0; i < N; i++)
{
int min = i;
for (int j = i; j < N; j++)
{
if (a[j] < a[min])
min = j;
}
swap(&a[i], &a[min]);
}
}
3.插入排序
时间复杂度:平均:O(N^2),最坏:O(N^2),最好:O(N)
思想:默认认为位置0-(i-1)是排好序的。将位置i上元素赋给临时变量,与左边一个值判断,若大于左边的,则位置不动。若小于左边的,则将左边的值移到当前位置,临时变量再继续同下一个左边的元素进行比较。
分析:直接插入排序性能要比冒泡排序和选择排序好一些。
代码:
void InsertSort(int *a, int N)
{
if (a == NULL || N <= 0)
return ;
int i, j;
for (i = 1; i < N; i++)
{
int tmp = a[i];
for (j = i; j > 0; j--)
{
if (tmp < a[j - 1])
a[j] = a[j - 1];
else
break;
}
a[j] = tmp;
}
}
4.希尔排序
时间复杂度:平均:O(N^3/2),最坏:O(N^2),最好:O(N)
思想:希尔排序又叫减小增量排序。本质是分组的插入排序。先将元素分成几个分组,每个分组进行插入排序。然后不断减小分组的间隔,直至间隔为1,就变成了插入排序。分析:增量序列的最后一个增量值必须为1才行。希尔排序并不稳定。
代码:
void ShellSort(int *a, int N)
{
if (a == NULL || N <= 0)
return ;
int gap;
int i, j;
for (gap = N/2; gap > 0; gap /= 2)
for (i = gap; i < N; i++) //实质是插入排序,将a[i]和左边比较,插入到正确位置
{
int tmp = a[i];
for (j = i; j >= gap; j -= gap) //和插入排序的操作一样
{
if (a[j - gap] > tmp)
a[j] = a[j - gap];
else
break; //默认为左边是已经排序的,若比左边的大,说明此时是正确的位置。
}
a[j] = tmp;
}
}
5.堆排序
时间复杂度:平均:O(N*logN),最坏:O(N*logN),最好:O(N*logN)
思想:先将输入数组进行堆化操作,构成一个最小堆,每次将堆顶元素与末尾元素互换,再将剩下的元素恢复堆序。
若使用最小堆,则排序后是递减;若使用最大堆,则排序后是递增。
分析:1.结点i的左右子节点下标分别为2i+1,2i+2;2.结点i的父结点下标为(i-1)/2;3.一颗完全二叉树的最后一个拥有子结点的结点下标为(n/2 - 1),n为结点数
代码:
//下滤操作
void MinHeapFixDown(int *a, int i, int n)
{
if (a == NULL || i < 0 || n < 0 || i >= n)
return ;
int tmp = a[i];
int j = 2 * i + 1; //左子结点
while (j < n)
{
if (j + 1 < n && a[j + 1] < a[j]) //取左右子结点中小的那个
j++;
if (tmp <= a[j]) //父结点比子结点小
break;
a[i] = a[j]; //将子结点上滤,移到父结点位置
i = j; //将父结点移到子结点位置
j = 2 * i + 1; //取子结点的子结点
}
a[i] = tmp;
}
//堆排序
void HeapSort(int *a, int n) //用最小堆排序后是递减,用最大堆排序后是递增。
{
if (a == NULL || n <= 0)
return ;
int i;
for (i = n / 2 - 1; i >= 0; i--) //堆化操作
MinHeapFixDown(a, i, n);
for (i = n - 1; i > 0; i--)
{
swap(&a[0], &a[i]); //交换堆顶元素和堆末尾元素
MinHeapFixDown(a, 0, i); //恢复剩余元素的堆序性质
}
}
6.归并排序
时间复杂度:平均:O(N*logN),最坏:O(N*logN),最好:O(N*logN)
思想:若有两个已排序的数组,合并时谁小就取谁。合并排序就是将左、右边排序后再合并,对于左边部分可递归的再分成左右两部分,每部分拍好序后合并形成左边排序的部分。右边也一样。
void Merge(int *a, int first, int mid, int last, int *tmp)
{
int lpos = first;
int rpos = mid + 1;
int pos = 0;
while (lpos <= mid && rpos <= last)
{
if (a[lpos] < a[rpos])
tmp[pos++] = a[lpos++];
else
tmp[pos++] = a[rpos++];
}
while (lpos <= mid)
tmp[pos++] = a[lpos++];
while (rpos <= last)
tmp[pos++] = a[rpos++];
for (int i = 0; i < pos; i++)
a[first + i] = tmp[i];
}
void MSort(int *a, int first, int last, int *tmp)
{
if (first < last)
{
int mid = (first + last) / 2;
MSort(a, first, mid, tmp); //左边有序
MSort(a, mid + 1, last, tmp); //右边有序
Merge(a, first, mid, last, tmp); //合并左右两个有序数列
}
}
void MergeSort(int *a, int n)
{
if (a == NULL || n <= 0)
return ;
int *tmp = new int[n];
if (tmp == NULL)
return ;
MSort(a, 0, n - 1, tmp);
delete [] tmp;
}
7.快速排序
时间复杂度:平均:O(N*logN),最坏:O(N^2),最好:O(N*logN)
思想:每次从数组中选出一个基准数,再将剩余元素中小于基准数的放在其左边,大于基准数的放在其右边。再继续对左右两部分继续进行相同操作。
分析:下面代码对一般的快速排序进行了优化。
优化部分有:
1.当剩余元素小于5(<=20)个时,进行插入排序,而不是全部都进行快速排序。优点:减小了递归深度,提升了效率。因为对于小数组的排序,快速排序不如插入排序快。
2.采用了三数中值法,而不是取数组中第1个元素。优点:当数组是准排序或逆序时,传统方法会将剩余元素全部移到一边,而三数中值法可避免这种情况,而且还可以提升排序效率。
3.在三数中值法的函数内,对左,中,右三个位置上元素进行排序。优点:本来它们就应该在该位置,提前将它们拍序了,还可以起到警戒标记,不用担心j越界。
4.将基准数放在right-1的位置,不用担心i越界。等一遍排序结束后,再将基准数和i位置元素进行交换。
int Median3(int *a, int left, int right)
{
int center = (left + right) / 2;
if (a[left] > a[center])
swap(&a[left], &a[center]);
if (a[left] > a[right])
swap(&a[left], &a[right]);
if (a[center] > a[right])
swap(&a[center], &a[right]);
swap(&a[center], &a[right - 1]);
return a[right - 1];
}
void QSort(int *a, int left, int right)
{
if (right - left >= 5)
{
int piovt = Median3(a, left, right);
int i = left;
int j = right - 1;
for (;;)
{
while (a[++i] < piovt) {}
while (a[--j] > piovt) {}
if (i < j)
swap(&a[i], &a[j]);
else
break;
}
swap(&a[i], &a[right - 1]);
QSort(a, left, i - 1);
QSort(a, i + 1, right);
}
else
InsertSort(a + left, right - left + 1);
}
void QuickSort(int *a, int n)
{
if (a == NULL || n <= 0)
return ;
QSort(a, 0, n - 1);
}