排序(数据结构与算法)

排序

通俗的定义:就是重新排列 表/集合 中的 元素/数据元素/记录, 使 表/集合 中的元素 满足按其关键字 递增或递减的过程。

严格定义:

输入: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  快速排序 算法复杂度

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值