排序02之选择排序

本文详细介绍了选择排序和堆排序两种经典排序算法。直接选择排序通过每次选取最小元素与前一个元素交换来排序,时间复杂度为O(n^2);树形选择排序则通过保存比较结果减少比较次数,但增加了空间复杂度。堆排序利用堆的特性,通过建堆和交换来排序,时间复杂度为O(nlogn),空间复杂度为O(1),但并非稳定排序。文中给出了C、Java和Python三种语言的代码示例。
摘要由CSDN通过智能技术生成

选择排序

基本方法:每次从待排序的文件中选择出排序码最小的记录,将该记录放到已排序文件的最后一个位置,直到已排序文件记录个数等于初始待排序文件的记录个数。

直接选择排序

定义

从所有n个待排序记录值红选择出排序码最小的记录,将该记录与第一个记录交换,再从剩下的n-1个记录中选择排序码最小的记录与第二个记录交换……,直到只剩下一个记录,排序完成。

图解

图源:https://www.cnblogs.com/hokky/p/8529042.html

代码

c
void simpleseselectsort(table *tab)
{
    int i,j,k;
    for(i=0;i<tab->length;i++)
    {
        k=i;
        for(j=i+1;j<tab->length;j++)
        {
            if(tab->r[j].key<tab->r[k].key) k=j;
        }
        if(k!=i)
        {
            tab->r[0]=tab->r[k];
            tab->r[k] = tab->r[i];
            tab->r[i]=tab->r[0];
        }
    }
}
Java
public static void main(String[] args) {
    int[] elem = {0,7,9,1,5,8,3,15,2,6};
    int i,k,temp;
    for (i = 0; i <elem.length ; i++) {
        k=i;
        for (int l = i+1; l <elem.length ; l++) {
            if(elem[l]<elem[k]){
                k=l;
            }
        }
        if(k!=i){
            temp=elem[k];
            elem[k]=elem[i];
            elem[i]=temp;
        }
    }
    for (int j = 0; j <elem.length ; j++) {
        System.out.println(elem[j]);
    }
}
python
def select_insert_sort(demo):
    print('排序前{}'.format(demo))
    length = len(demo)
    for i in range(0, length):
        k=i
        for j in range(i+1,length):
            if demo[j]<demo[k]:
                k=j
        if k!=i:
            temp = demo[k]
            demo[k] = demo[i]
            demo[i] = temp
    print("最终排序{}".format(demo))

算法要点

两重循环:

  1. 外层循环控制整体数组是否有序
  2. 内层循环找寻待排序序列中最小数放入已排序序列中

时间空间复杂度稳定性

  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)
  • 不稳定

举个例子,序列5 8 5 2 9,我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法。

树形选择排序

定义

在直接选择排序中,有许多排序码之间进行了不止一次比较。所以树形选择排序在选择排序码最小记录过程中,把其中的排序码比较的结果保存下来,使以后需要的时候直接看比较结果,而不再进行比较。

类似于生活中体育比赛的淘汰制。

图解

时间空间复杂度稳定性

  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(n)
  • 不稳定

然而虽然树形选择比较能够减少比较次数,却增加了辅助空间的使用。为了弥补此缺憾,威廉姆斯于1964年提出了堆排序

堆排序(大根堆)

既要保存中间比较结果,减少后面的比较次数,又不占用大量的附加存储空间,使算法有较好的性能。威廉姆斯于1964年提出了堆排序

堆排序的关键问题是如何将待排序记录的排序码建成一个堆,后排序。

大顶堆:所有父节点大于子节点

小顶堆:所有父节点小于子节点

详情见:https://www.bilibili.com/video/BV1Eb41147dK

代码

c
#include <stdio.h>
#include <stdlib.h>
void swap(int arr[],int i,int j)
{
    int temp=arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}
//从上而下
//单纯使用从上而下并不能保证最后一定是大顶堆
//例外:1,4,5,3,2,10   这样执行单纯从上到下无法将10送入顶端
void heapify(int tree[],int n,int i)
{

    int c1 =2*i+1;
    int c2= 2*i+2;
    int max=i;
    if(c1<n &&tree[c1]>tree[max])
    {
        max=c1;
    }
    if(c2<n&&tree[c2]>tree[max])
    {
        max=c2;
    }
    if(max!=i)
    {
        swap(tree,max,i);
        heapify(tree,n,max);
    }
}
//从下而上
//单纯从下而上也不能最终得到大顶堆
//例外:1,4,5,3,2,10 最后1即便是最小也无法处于叶子节点上
void build_heap(int tree[],int n)
{
    int last_node = n-1;
    int parent = (last_node-1)/2;
    int i;
    //所以我们需要先从下而,在从下而上处理的子树中从上而下再调整一遍。
    for(i=parent;i>=0;i--)
    {
        heapify(tree,n,i);
    }
    //最后即可得出正确的大顶堆

}
//大顶堆排序
void heap_sort(int tree[],int n)
{
    build_heap(tree,n);//先构成大顶堆
    int i;
    for(i=n-1;i>=0;i--)
    {
        swap(tree,i,0);
        //其实使用build_heap也同样达到效果,但是更繁琐。
        //build_heap(tree,i);
        heapify(tree,i,0);//交换之后,除了最上面的结点,其他部分都是堆,符合heapify标准,所以可以直接自上而下就可以成堆。
    }
}
int main()
{
    int tree[] = {1,4,5,3,2,10};
    int size = 6;
    heap_sort(tree,size);
    int i;
    for(i=0;i<size;i++)
    {
        printf("%d\n",tree[i]);
    }
    return 0;
}
Java
public class HelloWorld {

    public static void main(String[] args) {
        int[] tree = {1,4,5,3,2,10};
        int size = tree.length;
        heap_sort(tree,size);
        for (int j = 0; j <tree.length ; j++) {
            System.out.println(tree[j]);
        }
    }
    public static void swap(int[] arr,int i,int j){
        int temp=arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
    public static void heapify(int[] tree,int n,int i){
        int c1=2*i+1;
        int c2=2*i+2;
        int max = i;
        if(c1<n&&tree[c1]>tree[max]){
            max = c1;
        }
        if(c2<n && tree[c2]>tree[max]){
            max=c2;
        }
        if(max!=i){
            swap(tree,max,i);
            heapify(tree,n,max);
        }

    }
    public static void build_heap(int[] tree,int n){
        int last_node = n-1;
        int parent = (last_node-1)/2;
        int i;
        for(i=parent;i>=0;i--){
            heapify(tree,n,i);
        }
    }
    public static void heap_sort(int[] tree,int n){
        build_heap(tree,n);
        int i;
        for(i=n-1;i>=0;i--){
            swap(tree,i,0);
            heapify(tree,i,0);
        }
    }
}
python
def swap(tree, i, j):
    temp = tree[i]
    tree[i] = tree[j]
    tree[j] = temp
def heapify(tree, n, i):
    c1 = 2*i+1
    c2 = 2*i+2
    max = i
    if c1 < n and tree[c1] > tree[max]:
        max = c1
    if c2 < n and tree[c2] > tree[max]:
        max = c2
    if max != i:
        swap(tree, max, i)
        heapify(tree, n, max)

def build_heap(tree, size):
    last_node = size-1
    parent = (last_node-1)//2
    for i in range(parent, -1, -1):
        heapify(tree, size, i)

def heap_sort(num_list, size):
    build_heap(num_list, size)
    for i in range(size-1, -1, -1):
        swap(num_list, i, 0)
        heapify(num_list, i, 0)

if __name__ == '__main__':
    num_list = [1, 4, 5, 3, 2, 10, 6, 8, 18]
    length = len(num_list)
    heap_sort(num_list, length)
    print("最终排序{}".format(num_list))

算法要点

  1. 自上而下heapify(C1和C2和i三者找最大然后交换,递归自上而下执行)
  2. 交换swap
  3. 自下而上build_heap(从n-1到0,循环执行自上而下heapify
  4. 排序heap_sort(先建堆)

时间空间复杂度稳定性

  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(1)
  • 不稳定

我们知道堆的结构是节点i的孩子为(计数从0 开始)2 i + 2和2 i + 1节点。在一个长为n 的序列,堆排序的过程是从第n / 2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为n / 2 - 1, n / 2 - 2, … 1这些个父节点选择元素时,就会破坏稳定性。有可能第n / 2个父节点交换把后面一个元素交换过去了,而第n / 2 - 1个父节点把后面一个相同的元素没 有交换,那么这2个相同的元素之间的稳定性就被破坏了。所以,堆排序不是稳定的排序算法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值