排序
通俗的定义:就是重新排列 表/集合 中的 元素/数据元素/记录, 使 表/集合 中的元素 满足按其关键字 递增或递减的过程。
严格定义:
输入:n个记录R1 R2 ......Rn, 对应的关键字为 K1 K2 ......Kn.
输出:输入序列的一个重排: R1’ R2’......Rn’, 使得对应的关键字满足 K1’ <= k2’ <= ...... <=Kn’
例子:对所有学生按 总分进行排序,如果总分相等,再按 语数外总分进行排序。
从这个例子中看出,多个关键字的排序 最终都可以 转化为 单个关键字的 排序,因此下面的讨论的是 单个关键字的 排序。
几个特征
排序的稳定性/排序算法的稳定性:
定义:Ki = kj ( 1<= i <= n, 1<= j <= n, i≠j), 在排序前 Ri 在Rj 前面 (即 i <j)。 如果排序后Ri 仍然在 Rj 前面, 则称所用的 排序方法 是稳定的; 反之,如果可能使 排序后的 Rj 在Ri 前面,则称 所有的排序算法 是 不稳定的。
内排序 与 外排序
根据在排序的过程中,待排序的记录 是否全部 被放置在内存中,排序分为:内排序和 外排序。
对于内排序来说 影响其 时间性能/时间复杂度 的是:比较次数 和 移动次数
0 最普通双层for循环排序
c语言版本代码如下:
/* 最普通的排序 双for循环排序/冒泡排序初级版 */
void bubbleSort0(int a[], int length)
{
int i,j,temp;
for( i = 0; i <= length - 2; i++){
for( j = i+1; j <= length - 1; j++)
{
if(a[i] > a[j])
{
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
}
}
/*
缺陷: 排序完1, 2的位置后,对其余的关键字的排序没什么帮助(数字3反而被换到了最后一位)。
*/
php语言 版本的代码如下:
function bubbleSort0(array $arr)
{
$length = count($arr);
for($i = 0; $i <= $length - 2 ; $i++){
for($j = $i+1; $j <= $length - 1; $j++){
if($arr[$i] > $arr[$j]){
$tmp = $arr[$i];
$arr[$i] = $arr[$j];
$arr[$j] = $tmp;
}
}
}
return $arr;
}
$arr = [9, 1, 5, 8, 3, 7, 4, 6, 2];
$newArr = bubbleSort0($arr);
print_r($newArr);
i= 0 ,i = 1 排序过程(比较和交换) 如下图
图的说明:
观察后发现,在排序好 index=0 ,1 的位置后,对其余关键字的排序没有什么帮助(数字3反而被换到了最后一位)。
1 冒泡排序 Bubble Sort
定义: 从底部开始 两两比较相邻记录的关键字,如果反序则交换,选出本轮最小的,(第一轮选出最小的那个放到 0 索引位,第二轮选出倒数第二小的放到 1索引位....依次)。这样的过程要循环n-1次(即外层循环要n-1次)。
假设待排序的关键字序列是{9,1,5,8,3,7,4,6,2}
i=0; (即第一趟)时,排序的过程 (比较 和交换)如下图:
图的说明:
1. 每一列代表 数组的一个状态, 中间的 箭头表示一个操作(包含 比较+交换);图中有 9个状态,8个操作。
2. 如果把 两两比较 画作 一个圆圈(就像气泡),那么这种排序方法 在视觉上 就和 冒泡 有共同的特征了。
c语言版本代码如下:
#include <stdio.h>
#include <stdlib.h>
/* 冒泡排序
* 每次内循环 都从最后两个的比较开始;,内循环一次就会产生一 小值 浮到最上边。
* @param a 数组
* @param length 数组大小
*/
void bubbleSort(int a[], int length)
{
int i,j,temp;
for(i = 0; i < length - 1; i++)
{
for(j= length-2; j >= i; j--) //注意 j 是从后往前循环
{
if(a[j] > a[j+1]) // 如果 前者 大于 紧挨着的 后者
{
temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}
}
#define N 9
int main()
{
int i;
int arr[N] = {9,1,5,8,3,7,4,6,2};
printf("排序前:");
for(i = 0; i < N-1; i++)
printf("%d,", arr[i]);
printf("%d", arr[i]);
printf("\n");
bubbleSort(arr, N);
printf("排序后:");
for(i = 0; i < N-1; i++)
printf("%d,", arr[i]);
printf("%d", arr[i]);
printf("\n");
}
php版本的代码如下:
function bubbleSort(array $arr)
{
$length = count($arr);
for($i = 0; $i < $length - 1; $i++)
{
for($j = $length - 2; $j >= $i; $j--)
{
if($arr[$j] > $arr[$j + 1]){
$tmp = $arr[$j];
$arr[$j] = $arr[$j+1];
$arr[$j + 1] = $tmp;
}
}
}
return $arr;
}
$arr = [9, 1, 5, 8, 3, 7, 4, 6, 2];
$newArr = bubbleSort($arr);
print_r($newArr);
1. 1 冒泡排序的优化
如果待排序的序列是{2,1,3,4,5,6,7,8,9} ,也就是说,除了 index = 0, 1 的关键字需要交换外,别的都已经是正常的顺序。
但是前面的冒泡算法还是 i=1, 2 ...7 这么 一趟趟的 比较和交换。
方案:增加一个标记flag 值 为 true 就是需要外层循环,false 就不需要外层循环, 同时 true 就是 发生了交换
#include <stdio.h>
#include <stdlib.h>
void bubbleSortWithFlag(int a[], int length)
{
int i,j,temp;
int flag = 1;
for(i = 0; i < length - 1 && flag; i++)
{
printf("第%d 趟开始:\n", (i+1));
flag = 0;
for(j= length-2; j >= i; j--) //注意 j 是从后往前循环
{
if(a[j] > a[j+1]) // 如果 前者 大于 紧挨着的 后者
{
temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
flag = 1;
}
}
}
}
#define N 9
int main()
{
int i;
//int arr[N] = {9,1,5,8,3,7,4,6,2};
int arr[N] = {2,1,3,4,5,6,7,8,9};
//int arr[N] = {3,1};
printf("排序前:");
for(i = 0; i < N-1; i++)
printf("%d,", arr[i]);
printf("%d", arr[i]);
printf("\n");
bubbleSortWithFlag(arr, N);
printf("排序后:");
for(i = 0; i < N-1; i++)
printf("%d,", arr[i]);
printf("%d", arr[i]);
printf("\n");
}
[xingqiji@localhost sort]$ ./a.out
排序前:2,1,3,4,5,6,7,8,9
第1 趟开始:
第2 趟开始:
排序后:1,2,3,4,5,6,7,8,9
[xingqiji@localhost sort]$
php版本代码:
function bubbleSortWithFlag($arr)
{
$length = count($arr);
$flag = true;
for($i = 0; $i < $length - 1 && $flag; $i++)
{
printf("第%d 趟开始:\n", ($i+1));
$flag = false;
for($j = $length -2; $j >= $i; $j--)
{
if($arr[$j] > $arr[$j+1]){
$tmp = $arr[$j];
$arr[$j] = $arr[$j+1];
$arr[$j+1] = $tmp;
$flag = true;
}
}
}
return $arr;
}
$arr = [2, 1, 3, 4, 5, 6, 7, 8, 9];
$newArr = bubbleSortWithFlag($arr);
print_r($newArr);
1.2 冒泡算法的时间复杂度
普通冒泡排序:
改进后的冒泡排序:
2. 简单选择排序 (Simple Selection Sort)
就是通过 n- (i+1) 次 关键字 之间的比较,从 n - i 个记录中 选出 关键字最小的记录 ,并和 第 i (0 <= i <= n-2 )个 记录 交换 (i :是索引的含义, i+1 可以是趟含义 如 第 i+1 趟)
C语言代码如下:
#include <stdio.h>
#include <stdlib.h>
/*
对顺序表L 简单选择排序
*/
void SelectSort(int a[], int length)
{
int i,j,min , temp;
for(i=0; i < length-1; i++)
{
min = i; //将当前下标定义为最小值
for(j = i+1; j < length; j++)
{
if(a[min] > a[j])
min = j;
}
if(i!=min){ //如果 min 不等于 i,说明找到最小值,交换
// 交换a[i] 与 a[min] 的值。
temp = a[i];
a[i] = a[min];
a[min] = temp;
}
}
}
#define N 9
int main()
{
int i;
int arr[N] = {9,1,5,8,3,7,4,6,2};
printf("排序前:");
for(i = 0; i < N-1; i++)
printf("%d,", arr[i]);
printf("%d", arr[i]);
printf("\n");
SelectSort(arr, N);
printf("排序后:");
for(i = 0; i < N-1; i++)
printf("%d,", arr[i]);
printf("%d", arr[i]);
printf("\n");
}
php版本代码如下:
function selectSort(array $arr)
{
$length = count($arr);
for($i = 0; $i < $length - 1; $i++)
{
$min = $i; //将当前下标定义为为 最小值
for($j = $i + 1; $j < $length; $j++){
if($arr[$min] > $arr[$j]){
$min = $j;
}
}
if($i != $min) { // 说明找到了最小的值,交换
$tmp = $arr[$i];
$arr[$i] = $arr[$min];
$arr[$min] = $tmp;
}
}
return $arr;
}
$arr = [9, 1, 5, 8, 3, 7, 4, 6, 2];
$newArr = selectSort($arr);
print_r($newArr);
描述:
第一趟 :
i=0; 第1 趟(i+1); a[i] 即 a[0] 是 9 ; min = 0;
j = i+1 即 j=1 ; a[j] 和 a[i] 即 a[min] 比较,a[j] 小于 a[min] ,则 min= j ; j++ 继续 比较 a[j] 与 a[min] ...... 最终 min=1;
共比较了 n- (i+1) 次
min != i ,所以交换 a[min] 与 a[i];
共需要 n-1 趟
2.2 简单选择 排序 复杂度
比较次数 : (n-1) + (n-2) + ... + 1 = n(n-1)/2 ; 时间复杂度 O(n^2)
交换次数: 最好的时候 0次 , 最差的时候(一开始降序时),交换次数为 n-1 次; 时间复杂度:O(n)
最终的 排序时间复杂度是 比较与交换次数总和:O(n^2)
3. 直接插入排序 (Straight Insertion Sort)
将一个记录插入到已经排好序的有序表中, 从而得到一个新的,记录数增1的 有序表。
C语言代码如下:
#include <stdio.h>
#include <stdlib.h>
/**
* 插入排序
*/
void InsertSort(int a[], int length)
{
int i,j;
for(i=1; i < length; i++)//循环从第2个元素开始
{
if(a[i]<a[i-1])
{
int temp=a[i];
a[i] = a[i-1]; //将i-1 对应的 值 后移一位
for(j=i-2; j>=0 && a[j]>temp; j--)
{
a[j+1]=a[j]; // 将j 对应的 值 后移一位
}
a[j+1]=temp;//此处就是a[j+1]=temp;
}
}
}
#define N 9
int main()
{
int i;
int arr[N] = {9,1,5,8,3,7,4,6,2};
printf("排序前:");
for(i = 0; i < N-1; i++)
printf("%d,", arr[i]);
printf("%d", arr[i]);
printf("\n");
InsertSort(arr, N);
printf("排序后:");
for(i = 0; i < N-1; i++)
printf("%d,", arr[i]);
printf("%d", arr[i]);
printf("\n");
}
描述:
从第二个元素开始:i=1; 比较与 前一个元素 i-1 ,如果 比前一个小 ,则 :
前一个元素后移;
继续 比较 i即 temp 与 前前元素j ,如果 比前前元素 j 还小,则 j 对应的元素 后移一位
......
将 最后一个 小于 temp 的元素对应的 位置上 用 temp 赋值上。
php语言代码如下:
function insertSort($arr)
{
$length = count($arr);
for($i=1; $i < $length; $i++){
if($arr[$i] < $arr[$i-1])
{
$tmp = $arr[$i];
$arr[$i] = $arr[$i-1]; //将 i-1 对应的值 后移一位
for( $j = $i-2; $j >= 0 && $arr[$j] > $tmp ; $j--)
{
$arr[$j+1] = $arr[$j]; //将 j 对应的值 后移一位
}
$arr[$j+1] = $tmp; // 一开始的 $i 对应的值 即 $tmp 就应该放在 $j+1 下标对应的空间中
}
}
return $arr;
}
3.2 直接插入排序 的时间复杂度
最好的情况:例如:{2,3,4,5,6}
比较次数是 :n-1 次
最坏的情况:例如:{6,5,4,3,2}
比较的次数: 1 + 2 + ... + (n-1) = n(n-1)/ 2 ;
移动的次数 / 赋值的次数(准确的说) : 3 + 4 + ... + (n+1) = (n+4)(n-1)/2 ;
总的来说时间复杂度: O(n^2)
4. 希尔排序 (可以看做 是直接插入排序 的改进)
对待排序的 集合 进行 分组,分组方式是:将相距某个 “增量”的 记录 组成 一个子序列, 这样才能保证在子序列内分别进行直接插入排序后得到的 结果是 基本有序 而不是 局部有序。
C语言代码如下:
#include <stdio.h>
#include <stdlib.h>
// 声明打印函数
void printArray(int a[], int length);
/**
* 希尔排序
*/
void ShellSort(int a[], int length)
{
int i,j,k=0, temp;
int increment = length;
do
{
increment = increment/3 + 1;
for(i=increment; i< length; i++) // 注意是依次轮流每个组的一个元素来 进行插入排序;而不是把一个组完全排好,再排其他组。
{
if(a[i] < a[i-increment]) // 若成立:需要将a[i] 插入有序增量子表
{
temp = a[i];
a[i] = a[i-increment]; // 将 i-increment对应的值 后移一位
for(j= i- 2 * increment; j >= 0 && temp < a[j]; j-=increment)
a[j+increment] = a[j]; // 记录后移
a[j+increment] = temp; //插入
}
}
printf(" 第%d 次分组排序结果:", ++k);
printArray(a, length);
}
while(increment > 1);
}
/**
* 打印数组
*/
void printArray(int a[], int length)
{
int i;
for(i=0; i< length-1; i++)
printf("%d,", a[i]);
printf("%d", a[i]);
printf("\n");
}
#define N 9
int main()
{
int i;
int arr[N] = {9,1,5,8,3,7,4,6,2};
printf("排序前:");
printArray(arr, N);
ShellSort(arr, N);
printf("排序后:");
printArray(arr, N);
}
php语言代码:
function shellSort(array $arr)
{
$length = count($arr);
$increment = $length;
$k = 0;
do{
$increment = floor($increment/3) + 1;
for($i = $increment; $i < $length; $i++){
if($arr[$i] < $arr[$i - $increment])
{
$tmp = $arr[$i];
$arr[$i] = $arr[$i - $increment]; //将 i-increment对应的值 后移一位
for($j = $i - 2*$increment; $j >= 0 && $tmp < $arr[$j]; $j -= $increment){
$arr[$j + $increment] = $arr[$j]; //记录后移
}
$arr[$j + $increment] = $tmp;
}
}
printf("increment=%d ,第%d次分组排序结果:\n", $increment, ++$k);
print_r($arr);
}while($increment > 1);
return $arr;
}
描述:
按 步距 increment 来进行分组
依次轮流每一个组的一个元素 ,来进行插入排序 (注意不是:完全地把一个组里的元素 都 排完序,再进行下一组。)
4.2 希尔排序的 时间复杂度
4.3 希尔排序的 稳定性
不稳定
5. 堆排序
6. 归并排序
采用分治法(Divide and Conquer) 。 将已有序 的 子序列 合并, 得到完全有序的 序列。
6.1 代码实现
C语言代码:
#include <stdio.h>
#include <stdlib.h>
// 声明打印函数
void printArray(int a[], int length);
/**
* 比较合并2个 子序列
*/
void Merge(int sourceArr[], int tempArr[], int startIndex, int midIndex, int endIndex)
{
int i = startIndex, j=midIndex + 1, k = startIndex;
while(i != midIndex+1 && j!= endIndex + 1)
{
if(sourceArr[i] > sourceArr[j])
tempArr[k++] = sourceArr[j++];
else
tempArr[k++] = sourceArr[i++];
}
while(i != midIndex + 1)
tempArr[k++] = sourceArr[i++];
while(j != endIndex + 1)
tempArr[k++] = sourceArr[j++];
for(i= startIndex; i<= endIndex; i++)
sourceArr[i] = tempArr[i];
}
/**
* 划分子序列,并递归划分子序列,并最后,调用“比较合并2个 子序列”
*/
void MergeSort(int sourceArr[], int tempArr[], int startIndex, int endIndex)
{
int midIndex;
if(startIndex < endIndex)
{
midIndex = startIndex + (endIndex - startIndex) / 2; //
MergeSort(sourceArr, tempArr, startIndex, midIndex);
MergeSort(sourceArr, tempArr, midIndex+1, endIndex);
Merge(sourceArr, tempArr, startIndex, midIndex,endIndex);
}
}
/**
* 打印数组
*/
void printArray(int a[], int length)
{
int i;
for(i=0; i< length-1; i++)
printf("%d,", a[i]);
printf("%d", a[i]);
printf("\n");
}
#define N 9
int main()
{
int i;
int arr[N] = {9,1,5,8,3,7,4,6,2};
printf("排序前:");
printArray(arr, N);
//ShellSort(arr, N);
int b[9];
MergeSort(arr, b, 0, 8);
printf("排序后:");
printArray(arr, N);
}
描述:
第一步:申请空间,使其大小为 两个已排序序列之和,该空间用来存放合并后的序列。
第二步:设定两个指针,最初的位置分别为 两个已经排序序列的 起始位置。
第三步:比较两个指针指向的元素,选择相对较小的元素放到合并空间,并移动指针到下一个位置。
重复步骤3 直到某一个指针超出序列范围
将另一序列 所剩下的所有元素 直接复制 到合并 序列尾。
非递归算法:
6.2 归并排序 算法 复杂度
TimeSort 是 归并排序的 终极优化版本, 主要思想是:检测序列中的天然有序子段(若检测到严格降序子段则翻转序列为升序子段)。在最好情况下无论升序还是降序都可以使时间复杂度降至为O(n),具有很强的自适应性。
6.3 算法稳定性:
稳定
6.3 用途
6.4 比较
7. 快速排序
流程描述:
1: 首先设定一个分界值,通过该分界将数组分成左右两部分。
2: 将大于 分界值的数据 集中到 右边 ,小于等于 分界值的 集中到 左边。
3: 然后 左边和 右边 数据 又可以独立排序。对于左侧的数据,又可以取 一个分界值 ,同样的 右侧的数据,又可以取一个分界值。
4: 重复 上述的过程。当左右两个部分的数据 排序完成后,整个数组的排序也就完成了。
7.1 代码实现
C语言代码:
/**
* 快速排序
*/
void QuikSort(int *a, int low, int high)
{
if(low >= high){
return ;
}
int i= low;
int j = high;
int key = a[low];
while(i < j){
while(i < j && key <= a[j])
{
j--; /* 向前寻找 */
}
a[i] = a[j];
while(i < j && key >= a[i])
{
i++;
}
a[j] = a[i];
}
a[i] = key ;
QuikSort(a, low, i -1);
QuikSort(a, i+1, high);
}
7.2 快速排序 算法复杂度