排序(三)选择排序 c/c++与python实现

选择排序(Selection Sort)

选择排序的基本思想:每次会从未排序区间中找到最小的元素,将其放到已排序区间的末尾。主要有简单选择排序,堆排序。

简单选择排序

简单选择排序的基本思想:每次从未排序区间中找到一个最小的元素,与未排序区间的第一个元素交换,这样每次就可以确定一个元素的最终的位置,重复n-1,就完成了对n个数据的排序。

#include <stdio.h>
#include <stdlib.h>
// 简单选择排序c实现,a表示数组,n表示数组大小
/**
 * Author: gamilian
*/
void selection_sort(int a[], int n){
	for (int i=0; i < n - 1; i++){ 	//n-1次排序,n-1个元素确定了位置,最后一个元素自然确定了位置。
		int min = i;				//最小值的位置
		for (int j = i; j < n; j++)
			if(a[j] < a[min]){		//打擂台形式找出未排序序列最小值下标
				min = j;
			}
		if (min != i){				//若最小值下标改变则交换,用if减少了不必要的交换(最小值下标未变)
			int temp = a[i];
			a[i] = a [min];
			a[min] = temp;
		}	
	}
}

#	简单选择排序python实现
"""
    Author: gamilian
"""
def selection_sort(a):
	'''简单选择排序
		args:
			a: List[int]
	'''
	length = len(a)
	if length <= 1:
		return
	for i in range(0, length - 1):
		min = i
		for j in range(i, length):
			if a[j] < a[min]:
				min = j
		if min != i:
			a[min], a[i] = a[i], a[min]

算法的稳定性:选择排序每次都要找剩余未排序元素中的最小值,并和前面的元素交换位置,这样破坏了稳定性。所以选择排序是一种不稳定的排序算法

空间复杂度:从实现过程可以很明显地看出,简单选择排序算法的运行并不需要额外的存储空间,所以空间复杂度是 O(1),也就是说,这是一个原地排序算法

时间复杂度:简单选择排序的最好情况时间复杂度、最坏情况和平均情况时间复杂度都为 O(n^2)

堆排序(Heap Sort)

堆的性质

  • 堆是一个完全二叉树;

  • 堆中每一个节点的值都必须大于等于(或小于等于)其子树中每个节点的值,即堆中每个节点的值都大于等于(或者小于等于)其左右子节点的值

  • 对于每个节点的值都大于等于子树中每个节点值的堆,我们叫作“大顶堆”。对于每个节点的值都小于等于子树中每个节点值的堆,我们叫作“小顶堆”。

堆的表示:由于堆是一颗完全二叉树,而完全二叉树比较适合用数组来存储。用数组来存储完全二叉树是非常节省存储空间的。因为我们不需要存储左右子节点的指针,单纯地通过数组的下标,就可以找到一个节点的左右子节点和父节点。如图所示:
在这里插入图片描述从图中我们可以看到,数组中下标为 i 的节点的左子节点,就是下标为 i∗2 的节点,右子节点就是下标为 i∗2+1 的节点,父节点就是下标为 i/2 的节点,最后一个非叶节点编号为n/2。

堆化(heapify):也就是在一个堆中插入一个元素到堆的最后,然后调整新的完全二叉树,让其重新满足堆的特性的过程。

从下往上堆化:我们可以让新插入的节点与其父节点对比大小。如果不满足子节点小于等于父节点的大小关系,我们就互换两个节点。一直重复这个过程,直到父子节点之间满足刚说的那种大小关系。
在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
// 堆化c实现
/**
 * Author: gamilian
*/
typedef int HeapDataType; 
typedef struct Heap{
	HeapDataType* data;
	int count;  	//	堆中已经存储的数据个数
	int MaxSize; 	//	堆可以存储的最大数据个数
}HP;

//对于low-high的元素向上调整,low一般为1,high表示欲调整节点的下标
void upAdjust(HP* hp, int low, int high){
	int i = high, j = i / 2;				//i为欲调整节点的下标,j为其父节点
	while (j >= low){						//父节点在[low, high]内
		if (hp->data[j] < hp->data[i]){		//不满足子节点小于等于父节点的大小关系,则交换
			int tmp = hp->data[i];		
			hp->data[i] = hp->data[j];
			hp->data[j] = tmp;
			i = j;							//保持原来状态
			j = i / 2;
		}
		else
			break;
	}		
}

//插入元素自下往上堆化
void heapify(HP* hp, HeapDataType value){
	if (hp->count >= hp->MaxSize) 
		return; // 堆满了 
	hp->data[++hp->count] = value; 
	upAdjust(hp, 1, hp->count);
}

#	堆化python实现,见最后

删除堆顶元素:假设我们构造的是大顶堆,堆顶元素就是最大的元素。当我们删除堆顶元素之后,我们将我们把最后一个节点放到堆顶,然后利用同样的父子节点对比方法。对于不满足父子节点大小关系的,互换两个节点,并且重复进行这个过程,直到父子节点之间满足大小关系为止,即从上往下的堆化方法。

在这里插入图片描述

//删除堆顶元素c实现
/**
 * Author: gamilian
*/
//对于low-high的元素向下调整,low为欲调整节点的下标,high一般为堆中最后一个元素的下标
void downAdjust(HP* hp, int low, int high){
	int i = low, j = i * 2;					//i为欲调整节点的下标,j为其左孩子
	while (j <= high){						//孩子节点在[low, high]内
		if (j + 1 <= high && hp->data[j] < hp->data[j + 1])
			j = j + 1;						//右孩子存在且左孩子小于右孩子
		if (hp->data[j] > hp->data[i]){		//不满足子节点小于等于父节点的大小关系,则交换
			int tmp = hp->data[i];		
			hp->data[i] = hp->data[j];
			hp->data[j] = tmp;
			i = j;							//保持原来状态
			j = i * 2;
		}
		else
			break;
	}		
}

//删除堆顶元素	
void deleteTop(HP* hp){
	if (hp->count < 1)
		return;		//堆为空
	hp->data[1] = hp->data[hp->count--];
	downAdjust(hp , 1, hp->count);
}
#	删除堆顶元素python实现,见最后

堆排序(Heap Sort)

1、建堆

我们首先将数组原地建成一个堆。所谓“原地”就是,不借助另一个数组,就在原数组上操作。建堆的过程,有两种思路。

  • 第一种是借助之前在堆中插入一个元素的思路。尽管数组中包含 n 个数据,但是我们可以假设,起初堆中只包含一个数据,就是下标为 1 的数据。然后,我们调用前面讲的插入操作,将下标从 2 到 n 的数据依次插入到堆中。这样我们就将包含 n 个数据的数组,组织成了堆。
//第一种方法初始化堆,heapify算法
/**
 * Author: gamilian
*/
void initMaxHeap1(HP* hp, int size, HeapDataType* arr){
	hp->MaxSize = size;
	hp->data = (HeapDataType*)malloc((hp->MaxSize + 1) * sizeof(HeapDataType));//从1开始存储
	hp->data[1] = arr[0];
 	hp->count = 1;
	//把arr数组的值一个个插入到这个堆。
	for (int i = 1; i < size; i++)
	{
		heapify(hp , arr[i]);
	}	
}
#	第一种方法初始化堆,python实现,见最后
  • 第二种实现思路,跟第一种截然相反,第一种建堆思路的处理过程是从前往后处理数组数据,并且每个数据插入堆中时,都是从下往上堆化,而第二种实现思路,是从后往前处理数组,并且每个数据都是从上往下堆化。
//第二种方法初始化堆
/**
 * Author: gamilian
*/
void initMaxHeap2(HP* hp, int size, HeapDataType* arr){
	hp->MaxSize = size;
	hp->data = (HeapDataType*)malloc((hp->MaxSize + 1) * sizeof(HeapDataType));//从1开始存储
	//把arr数组的值赋给这个堆
	for (int i = 0; i < size; i++)
	{
		hp->data[i + 1] = arr[i];
	}
	hp->count = size;
 
	//整合堆操作,向下调整
	for (int i = hp->count / 2; i > 0; i--)
	{
		downAdjust(hp, i, hp->count);
	}
}
#	第二种方法初始化堆,python实现,见最后
2、排序

建堆结束之后,数组中的数据已经是按照大顶堆的特性来组织的。数组中的第一个元素就是堆顶,也就是最大的元素。我们把它跟最后一个元素交换,那最大元素就放到了下标为 n 的位置。这个过程有点类似上面讲的“删除堆顶元素”的操作,当堆顶元素移除之后,我们把下标为 n 的元素放到堆顶,然后再通过堆化的方法,将剩下的 n−1 个元素重新构建成堆。堆化完成之后,我们再取堆顶的元素,放到下标是 n−1 的位置,一直重复这个过程,直到最后堆中只剩下标为 1 的一个元素,排序工作就完成了。

// 堆排序c实现,a表示数组,n表示数组大小
/**
 * Author: gamilian
*/
void heap_sort(int a[], int n){
	HP hp;
	initMaxHeap1(&hp, n, a);		//建堆
	for (int i = n; i > 1; i--){	//交换堆顶与最后一个元素,并不断调整
		int tmp = hp.data[1];		
		hp.data[1] = hp.data[i];
		hp.data[i] = tmp;
		downAdjust(&hp, 1, i-1);
	}
	for (int i = 0; i < n; ++i)
	{
		a[i] = hp.data[i + 1];
	}
}
#	堆排序python实现
"""
    Author: gamilian
"""
from typing import Optional, List

class Heap:
    def __init__(self, capacity: int):
        self._data = [0] * (capacity + 1)
        self._capacity = capacity
        self._count = 0

    @classmethod
    def _parent(cls, child_index: int) -> int:
        """父节点索引"""
        return child_index // 2

    @classmethod
    def _left(cls, parent_index: int) -> int:
        """左孩子索引"""
        return 2 * parent_index

    @classmethod
    def _right(cls, parent_index: int) -> int:
        """右孩子索引"""
        return 2 * parent_index + 1
        
	#	向上调整
    def _upAdjust(self) -> None:
        i, parent = self._count, Heap._parent(self._count)
        while parent and self._data[i] > self._data[parent]:
            self._data[i], self._data[parent] = self._data[parent], self._data[i]
            i, parent = parent, Heap._parent(parent)
            
	#	向下调整
    @classmethod
    def _downAdjust(cls, a: List[int], count: int, root_index: int = 1) -> None:
        i = larger_child_index = root_index
        while True:
            left, right = cls._left(i), cls._right(i)
            if left <= count and a[i] < a[left]:
                larger_child_index = left
            if right <= count and a[larger_child_index] < a[right]:
                larger_child_index = right
            if larger_child_index == i: break
            a[i], a[larger_child_index] = a[larger_child_index], a[i]
            i = larger_child_index
            
	# 堆化
    def heapify(self, value: int) -> None:
        if self._count >= self._capacity: return
        self._count += 1
        self._data[self._count] = value
        self._upAdjust()
        
	#	删除堆顶元素
    def deleteTop(self) -> Optional[int]:
        if self._count:
            result = self._data[1]
            self._data[1] = self._data[self._count]
            self._count -= 1
            Heap._downAdjust(self._data, self._count)
            return result
            
	# 第一种方法建堆
    @classmethod
    def build_heap_1(cls, a: List[int]) -> None:
        """数组索引需要从1开始"""
        heap =cls(len(a) - 1)
        heap._data[0] = None
        heap._data[1] = a[1]
        heap._count = 1
        for i in range(2, len(a)):
            heap.heapify(a[i])
        for i in range(len(a)):
            a[i] = heap._data[i]
            
	#第二种方法建堆
    @classmethod
    def build_heap_2(cls, a: List[int]) -> None:
        """数组索引需要从1开始"""
        for i in range((len(a) - 1) // 2, 0, -1):
            cls._downAdjust(a, len(a) - 1, i)

    @classmethod
    def sort(cls, a: List[int]) -> None:
        """数组索引需要从1开始"""
        cls.build_heap_1(a)
        k = len(a) - 1
        while k > 1:
            a[1], a[k] = a[k], a[1]
            k -= 1
            cls._downAdjust(a, k)

    def __repr__(self):
        return self._data[1: self._count + 1].__repr__()


def heap_sort(a):
    a.insert(0, None)
    Heap.sort(a)
    a[:] = a[1:]

算法的稳定性:堆排序每次筛选时,可能把后面相同关键字的元素调整到前面,这样破坏了稳定性。所以选择排序是一种不稳定的排序算法

空间复杂度:从实现过程可以很明显地看出,堆排序算法的运行并不需要额外的存储空间,所以空间复杂度是 O(1),也就是说,这是一个原地排序算法
注:第一种方法建堆,需要额外的存储空间,空间复杂度O(n)

时间复杂度:堆排序的最好情况时间复杂度、最坏情况和平均情况时间复杂度都为 O(nlogn)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值