数据结构-选择排序

博客指出百度百科C语言简单选择排序有误,并进行了更正,分析其时间复杂度为O(n^2)。同时介绍堆排序,它利用堆结构,是选择排序的一种,时间复杂度为O(nlogn),还阐述了堆排序的基本思想、步骤,并提及后续会进行代码实现。

一、简单选择排序

该版百度百科关于C语言在简单选择排序上有误

更正:

#include<stdio.h>
int main()
{ 
int a[10],i,j,k,t;
printf("input 10 numbers:\n");
 for(i=0;i<10;i++)
   scanf("%d",&a[i]);
   printf("\n");
  for(i=0;i<10;i++)
  {
    k=i;
   for(j=i+1;j<10;j++){
       if(a[k]>a[j]){
	   k=j;
       }
   }
       if(k!=i){ 
		   t=a[i];
		   a[i]=a[k];
		   a[k]=t;
     }
   }
printf("the sorted numbers:\n");
for(i=0;i<10;i++)
        printf("%-3d",a[i]);
}

算法分析:

从i个记录中挑选最小记录需要比较i-1次,对n个记录进行简单选择排序,所需进行关键字比较次数总计为:

移动记录次数,正序为最小值0,反序为最大值3(n-1);

简单选择排序最好、最坏、平均时间复杂度都是O(n^2); 

 

二、堆排序

 

预备知识

堆排序

  堆排序是利用这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。首先简单了解下堆结构。

  堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:

同时,我们对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子

该数组从逻辑上讲就是一个堆结构,我们用简单的公式来描述一下堆的定义就是:

大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]  

小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]  

ok,了解了这些定义。接下来,我们来看看堆排序的基本思想及基本步骤:

堆排序基本思想及步骤

  堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了

步骤一 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。

  a.假设给定无序序列结构如下

2.此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。

4.找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。

这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。

此时,我们就将一个无需序列构造成了一个大顶堆。

步骤二 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。

a.将堆顶元素9和末尾元素4进行交换

b.重新调整结构,使其继续满足堆定义

c.再将堆顶元素8与末尾元素5进行交换,得到第二大元素8.

后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序

再简单总结下堆排序的基本思路:

  a.将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;

  b.将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;

  c.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

代码实现

#include <stdio.h>
#include <stdlib.h>

void swap(int *a, int *b) {
    int temp = *b;
    *b = *a;
    *a = temp;
}

void max_heapify(int arr[], int start, int end) {
    // 建立父節點指標和子節點指標,从下标0开始
    int dad = start;
    int son = dad * 2 + 1;
    while (son <= end) { // 若子節點指標在範圍內才做比較
        if (son + 1 <= end && arr[son] < arr[son + 1]) // 先比較兩個子節點大小,選擇最大的
            son++;
        if (arr[dad] > arr[son]) //如果父節點大於子節點代表調整完畢,直接跳出函數
            return;
        else { // 否則交換父子內容再繼續子節點和孫節點比較
            swap(&arr[dad], &arr[son]);
            dad = son;
            son = dad * 2 + 1;
        }
    }
}

void heap_sort(int arr[], int len) {
    int i;
    // 初始化,i從最後一個父節點開始調整,从下标0开始,len-1是最后一个元素,
    //由父节点推左子节点为2i+1可知,2i+1=len-1,故最后一个非叶节点下标是(len-1-1)/2即len/2-1;
    for (i = len / 2 - 1; i >= 0; i--)
        max_heapify(arr, i, len - 1);
    // 先將第一個元素和已排好元素前一位做交換,再重新調整,直到排序完畢
    for (i = len - 1; i > 0; i--) {
        swap(&arr[0], &arr[i]);
        max_heapify(arr, 0, i - 1);
    }
}

int main() {
    int arr[] = { 3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6 };
    int len = (int) sizeof(arr) / sizeof(*arr);
    heap_sort(arr, len);
    int i;
    for (i = 0; i < len; i++)
        printf("%d ", arr[i]);
    printf("\n");
    return 0;
}
### 构建二叉堆的时间复杂度分析 构建二叉堆的过程可以分为两种方式来理解其时间复杂度。 一种方法是从空堆开始逐个插入元素。如果通过这种方式建立,则每次插入操作的时间复杂度为 O(log n)[^2],因为每插入一个新的节点可能需要调整从新位置到根路径上的所有节点以维持最大/最小性质。对于含有 n 个元素的情况,总的时间开销将是 n 次这样的插入操作之和,即 O(n log n) 的上界估计。 然而,更高效的建堆算法不是基于单次插入而是自底向上地执行一系列筛选(sift-down)操作。该过程始于最后一个内部节点并向前推进直到到达根节点,在此期间修复违反堆属性的部分子树。这种方法能够在线性时间内完成整个堆的构造,具体来说是 O(n) 而非 O(n log n),这是因为并非所有的层都需要完整的对数级比较次数;实际上越靠近底部的层次所需的工作量越少[^1]。 ```python def build_heap(arr): n = len(arr) # Start from the last non-leaf node and sift down each element. for i in range(n // 2 - 1, -1, -1): sift_down(arr, i, n) def sift_down(heap, startpos, pos_limit): newitem = heap[startpos] childpos = 2 * startpos + 1 while childpos < pos_limit: rightpos = childpos + 1 if rightpos < pos_limit and not heap[childpos] > heap[rightpos]: childpos = rightpos heap[startpos] = heap[childpos] startpos = childpos childpos = 2 * startpos + 1 heap[startpos] = newitem # Restore the heap property by moving up or down as necessary. while startpos > 0: parentpos = (startpos - 1) >> 1 parent = heap[parentpos] if newitem > parent: heap[startpos] = parent startpos = parentpos continue break heap[startpos] = newitem ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值