《小灰的算法之旅 python版》总结

379 阅读11分钟

二叉树

树的构建

定义树结构

class TreeNode:
    def __init__(self, data):
        self.data = data
        self.left = None
        self.right = None

生成树

def create_binary_tree(input_list=[]):
    if input_list is None or len(input_list) == 0:
        return None
    data = input_list.pop(0)
    if data is None:
        return None
    node = TreeNode(data)
    node.left = create_binary_tree(input_list)
    node.right = create_binary_tree(input_list)
    return node

树的遍历

深度优先

递归实现

class TreeNode:
    def __init__(self, data):
        self.data = data
        self.left = None
        self.right = None


def create_binary_tree(input_list=[]):
    """
    构建二叉树
    :param input_list: 输入数列
    """
    if input_list is None or len(input_list) == 0:
        return None
    data = input_list.pop(0)
    if data is None:
        return None
    node = TreeNode(data)
    node.left = create_binary_tree(input_list)
    node.right = create_binary_tree(input_list)
    return node


def pre_order_traversal(node):
    """
    前序遍历
    :param node: 二叉树节点
    """
    if node is None:
        return
    print(node.data)
    pre_order_traversal(node.left)
    pre_order_traversal(node.right)
    return node


def in_order_traversal(node):
    """
    中序遍历
    :param node: 二叉树节点
    """
    if node is None:
        return
    in_order_traversal(node.left)
    print(node.data)
    in_order_traversal(node.right)
    return node


def post_order_traversal(node):
    """
    后序遍历
    :param node: 二叉树节点
    """
    if node is None:
        return
    post_order_traversal(node.left)
    post_order_traversal(node.right)
    print(node.data)
    return node


my_input_list = list([3, 2, 9, None, None, 10, None, None, 8, None, 4])
root = create_binary_tree(my_input_list)
print("前序遍历:")
pre_order_traversal(root) # 3,2,9,10,8,4
print("中序遍历:")
in_order_traversal(root) # 9,2,10,3,8,3
print("后序遍历:")
post_order_traversal(root) # 9,10,2,4,8,3

非递归实现

class TreeNode:
    def __init__(self, data):
        self.data = data
        self.left = None
        self.right = None


def create_binary_tree(input_list=[]):
    if input_list is None or len(input_list) == 0:
        return None
    data = input_list.pop(0)
    if data is None:
        return None
    node = TreeNode(data)
    node.left = create_binary_tree(input_list)
    node.right = create_binary_tree(input_list)
    return node

# 前序遍历
def pre_order_traversal_with_stack(node):
    stack = []
    while node is not None or len(stack) > 0:
        while node is not None:
            print(node.data)
            stack.append(node)
            node = node.left
        if len(stack) > 0:
            node = stack.pop()
            node = node.right
# 中序遍历
def in_order_traversal_with_stack(node):
    stack = []
    while node is not None or len(stack) > 0:
        while node is not None:
            stack.append(node)
            node = node.left
        if len(stack) > 0:   
            node = stack.pop()
            print(node.data)
            node = node.right
# 后序遍历
def post_order_traversal_with_stack(root):
        if not root:
            return list()
        
        res = list()
        stack = list()
        prev = None

        while root or stack:
            # 初始化和左节点存在
            while root:
                stack.append(root)
                root = root.left

            # 左节点不存在,根出栈,根据右节点是否存在判断是否为叶子节点
            # root = stack.pop()
            ### 不出栈也可以
            root = stack[-1]

            # 右节点不存在 或 右节点是前一个出栈的节点
            if not root.right or root.right == prev:
                ### 没有右节点的时候再出栈
                stack.pop()
                # 添加到res的是叶子节点或已被遍历完左右的根节点
                # res.append(root.data) # 可以不用res,直接 print(root.data)
                print(root.data)
                # 标记为root节点的右节点不存在或已出栈(已被遍历)
                prev = root 
                # 标记当前子树遍历完成,往上回溯
                root = None
            else:
                # 右节点存在,作为根入栈
                # 根节点是第二次入栈
                ### 没有出栈就没有二次进栈了
                # stack.append(root)
                # 遍历右节点
                root = root.right
        
        # return res
           
my_input_list = list([3, 2, 9, None, None, 10, None, None, 8, None, 4])
root = create_binary_tree(my_input_list)

print("前序遍历:")
pre_order_traversal_with_stack(root) # 3,2,9,10,8,4

print("中序遍历:")
in_order_traversal_with_stack(root) # 9,2,10,3,8,4

print("后序遍历:")
post_order_traversal_with_stack(root) # 9,10,2,4,8,3

广度优先(层序遍历)

利用队列实现层序遍历

from queue import Queue

def level_order_traversal(node):
    queue = Queue()
    queue.put(node)
    while not queue.empty():
        node = queue.get()
        print(node.data)
        if node.left is not None:
            queue.put(node.left)
        if node.right is not None:
            queue.put(node.right)


def create_binary_tree(input_list=[]):
    if input_list is None or len(input_list) == 0:
        return None
    data = input_list.pop(0)
    if data is None:
        return None
    node = TreeNode(data)
    node.left = create_binary_tree(input_list)
    node.right = create_binary_tree(input_list)
    return node


class TreeNode:
    def __init__(self, data):
        self.data = data
        self.left = None
        self.right = None


my_input_list = list([3, 2, 9, None, None, 10, None, None, 8, None, 4,None, None])
root = create_binary_tree(my_input_list)
print("层序遍历:")
level_order_traversal(root) # 3,2,8,9,10,4

树的补充

概念

节点的度:一个节点包含子树(直接后继)的数目,称为该节点的度

树的度:树中节点度的最大值称为树的度(唯一)

树的层数:根节点的层数为1,其他节点的层数为从根节点到该节点所经过的分支数据加1

森林:若干颗互不相交的树组成的集合为森林

路径和路径长度:从节点n1到nk的路径为一个节点序列n1,n2,...,nk,ni是ni+1的父节点。路径所包含边的个数为路径的长度。

二叉树的性质

(1)若二叉树的层次从1开始,则在二叉树第i层最多有pow(2,i-1) 个节点(i>= 1)

(2)深度为k的二叉树最多有pow(2,k)-1个节点(k >= 1)

(3)对任意一棵二叉树,如果叶子节点个数为n0,度为2的节点个数为n2,则no = 2n2 + 1

(4)如果将一棵有n个节点的完全二叉树的节点按层序(自顶向下,同一层自左向右)连续编号1,2,...,n,然后按此编号将树中各节点按顺序存放于一个一维数组中,并简称编号为i的节点为节点i(1 <= i <= n),则:

若 i = 1,则i是二叉树的根,无双亲

若 i > 1,则i的双亲为floor( i / 2 )(向下取整)

若 2i <= n,则i的左孩子为 2i,否则无左孩子

若 2i + 1 <= n,则i的有孩子为 2i + 1,否则无右孩子

若 i 为偶数,且 i != n,则其右兄弟为 i+1

若 i 为奇数,且 i != 1,则其左兄弟为 i - 1

i所在层为 floor( log(2,i) + 1)

完全二叉树:编号连续就是完全二叉树

线索二叉树

作用:(1)二叉树的二叉链表存储结构中,共有2n个指针域,其中有n+1个指针域未利用,浪费空间,引入线索化利用空间。

(2)不同遍历方式下得到的顺序是不同的,左右孩子和根节点出现的顺序并不一定是相邻的,这样无法直接得到一个节点在某种遍历方式下的直接前驱和直接后继,引入线索,则是增加了指向直接前驱和直接后继的指针。

在二叉树中加入线索:利用原有孩子指针为空时来存放直接前驱和后继。利用的是空闲指针,若节点有左孩子,则lchild指向其左孩子,否则lchild中存储的是该节点的前驱节点的指针;若节点有右孩子,则rchild指向其右孩子,否则rchild中存储的是该节点的后继节点的指针。实质上是对一个非线性结构进行线性化操作,使每个节点(除第一个和最后一个)在这些线性序列中有且仅有一个直接前驱和直接后继。

说明:在线索中的前驱和后继是按某种次序遍历所得到的序列中的前驱和后继。

其他

(1)区分孩子指针中存放的是左右孩子还是直接前驱与直接后继:在二叉链表节点中,增加两个标志域:ltag、rtag。ltag为0,则lchild为左孩子指针,lchild为1即是直接前驱;

(2)先序遍历+中序遍历,以及后续遍历+中序遍历,可以唯一确定一棵树。先序遍历可以确定根的相对位置,中序遍历可以根据与根的顺序确定是左还是右,以及确定是否存在左右子孩子。

比较排序

冒泡排序

基础冒泡

def dubble(list):
    for i in range(len(list)-1):
        for j in range(len(list)-1):
            if list[j] > list[j+1]:
                list[j],list[j+1] = list[j+1],list[j]

    return list

list = [4,2,6,9,1,7,3,5,8]
print(dubble(list))

第一次优化,减少外层循环次数

主要思路:增加一个label作为标记,判断是否已有序的依据是某一趟比较没有进行任何元素交换。

def dubble(list):
    for i in range(len(list)-1):
        label = 'true' # 新增代码
        for j in range(len(list)-1):
            if list[j] > list[j+1]:
                list[j],list[j+1] = list[j+1],list[j]
                label = 'false' # 新增代码
        if label == 'true': # 新增代码
            return list

    return list

list = [4,2,6,9,1,7,3,5,8]
print(dubble(list))

第二次优化,减少内层循环次数

主要思路:记录下每一趟最后交换的位置,从此处开始列表之后都是有序的,较少不必要的比较。

dubble.py

def dubble(list):
    lastExchangeLabel = len(list)-1 # 新增代码
    for i in range(len(list)-1):
        label = 'true'
        for j in range(lastExchangeLabel): # 注意这里循环长度的变化,是动态改变的
            if list[j] > list[j+1]:
                list[j],list[j+1] = list[j+1],list[j]
                label = 'false'
                lastExchangeLabel = j # 新增代码
        if label == 'true':
            return list

    return list

list = [4,2,6,9,1,7,3,5,8]
print(dubble(list))

快速排序

主要思想:分治法

双指针递归快速排序

主要思想:选定一个基准元素(可以取列表第一个元素),利用左右两个指针对列表分别从头部和尾部一次进行扫描,左指针左侧是小于基准元素的边界,右指针右侧是大于基准元素的边界。左右指针交替进行扫描,当左扫描到比基准元素大的元素,和当右指针扫描到比基准元素小的元素时,分别停止,交换扫描的这样两个元素(这样就是左扫描到比基准元素大的元素放在了右指针右侧,右指针扫描到比基准元素小的元素放在了左指针左侧)。直到两个指针相遇,这说明所有元素已经扫描完毕,以及按照基准元素分成了两个部分,此时指针重合点要和基准元素进行交换,基准元素如果选定在列表的第一个元素,那么需要保证的是指针重合点是比基准元素小的,因为交换之后指针重合点交换到了基准元素左侧。为了满足这一条件,循环开始时应该从右侧开始,即右指针先进行移动,这样能够保证指针重合点是比基准元素小的。指针重合点和基准元素交换之后,返回基准元素的位置,这样做是为了将列表分成两个部分,分别再进行排序,这个循环可以利用递归,也可以使用栈加while循环进行。

def quick(start_index,end_index,arr):
    # 递归结束条件,最后只有一个元素时,start_index是可能大于end_index的;最后两个元素时,start_index是可能等于end_index的
    if start_index >= end_index:
        return
    # 得到指针重合点索引,也是分而治之的分治点
    base = getBase(start_index,end_index,arr)
    # 递归,指针重合点base的索引是分界点
    quick(start_index,base-1,arr)
    quick(base+1,end_index,arr)

def getBase(start_index,end_index,arr):
    # 双边循环
    # 必须从右边开始,为了保证指针重合点小于基准元素
    basic = start_index
    # 循环的条件是左右指针不相等
    while start_index != end_index:
        # 因为在这个循环体内start_index核end_index可能会进行变化,所以需要判断start_index < end_index
        while start_index < end_index and arr[end_index] > arr[basic]:
            end_index -= 1
        # 基准元素可能会和元素值相等,对于左侧来说,第一个元素和基准元素是相等的
        while start_index < end_index and arr[start_index] <= arr[basic]:
            start_index += 1
        if start_index < end_index:
            arr[start_index],arr[end_index] = arr[end_index],arr[start_index]
    # 这里是左右指针相遇的操作
    # 交换指针重合点和基准元素
    arr[basic],arr[start_index] = arr[start_index], arr[basic]
    # 返回指针重合点(基准元素)的索引
    return start_index

list = [3,4,14,1,5,6,7,8,-1,0,3,1,3,9,11]

quick(0,len(list)-1,list)
print(list)

双指针非递归快速排序

主要思想:利用栈和while循环替代递归,栈的后进先出特点可以实现分而治之。

def quick(start_index,end_index,arr):
    # 非递归
    stack = []
    # 因为栈一次只能入一个值,而getBase需要两个参数,因此可以使用字典
    initIndex = {'start_index':start_index,'end_index':end_index}
    # 初始化栈中的值,这里只执行一次
    stack.append(initIndex)
    
    # 当栈为空时,就是已经没有可以再分的子列表了,这是循环结束
    while len(stack):
        # 取到左右指针
        index = stack.pop()
        start_index = index.get('start_index')
        end_index = index.get('end_index')
        # 将左右指针传入getBase函数并得到分界点
        base = getBase(start_index,end_index,arr)
        # 判断分界点是否还能进行分界,即是否还有分成两部分的需要
        # 分界点的左边
        if start_index < base-1:
            left = {'start_index':start_index,'end_index':base-1}
            stack.append(left)
        # 分界点的右边
        if base + 1 < end_index:
            right = {'start_index':base+1,'end_index':end_index}
            stack.append(right)

def getBase(start_index,end_index,arr):
    # 双边循环
    # 必须从右边开始,为了保证指针重合点小于基准元素
    basic = start_index
    while start_index != end_index:
        while start_index < end_index and arr[end_index] > arr[basic]:
            end_index -= 1
        while start_index < end_index and arr[start_index] <= arr[basic]:
            start_index += 1
        if start_index < end_index:
            arr[start_index],arr[end_index] = arr[end_index],arr[start_index]
    arr[basic],arr[start_index] = arr[start_index], arr[basic]
    return start_index

list = [3,4,14,1,5,6,7,8,-1,0,3,1,3,9,11]

quick(0,len(list)-1,list)
print(list)


单指针递归快速排序

主要思想:取一个基准元素作为分界,只利用一个指针作为标志位,索引列表,将列表元素分成两半,左侧是比基准元素小的,右侧是比基准元素大的。将所有元素遍历一次,最后将分界点和基准元素进行交换。

def quick(start_index,end_index,arr):
    # 递归
    if start_index >= end_index:
        return
    base = getBase(start_index,end_index,arr)
    quick(start_index,base-1,arr)
    quick(base+1,end_index,arr)

def getBase(start_index,end_index,arr):
    # 单边循环
    # 指针,即分界的标志位
    mark = start_index
    for i in range(start_index+1,end_index+1):
        # 元素小于基准元素
        if arr[i] < arr[start_index]:
            # 移动指针
            mark+=1
            # 交换元素,即比基准元素小的放在标志位左侧
            arr[i],arr[mark] = arr[mark],arr[i]
    # 交换基准元素和标志位(分界点)的值
    arr[start_index], arr[mark] = arr[mark], arr[start_index]
    return mark

list = [3,4,14,1,5,6,7,8,-1,0,3,1,3,9,11]

quick(0,len(list)-1,list)
print(list)


单指针非递归快速排序

def quick(start_index,end_index,arr):
    # 非递归
    stack = []
    initIndex = {'start_index':start_index,'end_index':end_index}
    stack.append(initIndex)
    
    while len(stack):
        index = stack.pop()
        start_index = index.get('start_index')
        end_index = index.get('end_index')
        base = getBase(start_index,end_index,arr)
        if start_index < base-1:
            left = {'start_index':start_index,'end_index':base-1}
            stack.append(left)
        if base + 1 < end_index:
            right = {'start_index':base+1,'end_index':end_index}
            stack.append(right)

def getBase(start_index,end_index,arr):
    # 单边循环
    mark = start_index
    for i in range(start_index+1,end_index+1):
        if arr[i] < arr[start_index]:
            mark+=1
            arr[i],arr[mark] = arr[mark],arr[i]
    arr[start_index], arr[mark] = arr[mark], arr[start_index]
    return mark

list = [3,4,14,1,5,6,7,8,-1,0,3,1,3,9,11]

quick(0,len(list)-1,list)
print(list)

《图解算法》中快速排序的实现

其代码及其解释强调递归,没有提及指针移动

def quicksort(arr):
	# 编写涉及数组的递归函数时,基线条件通常是数组为空或只包含一个元素
	if len(arr) < 2:
		return arr

	pivot = arr[0]
    # 这里比起单指针移动更加粗暴
	low = [i for i in arr[1:] if i <= pivot]
	height = [i for i in arr[1:] if i > pivot]

	return quicksort(low) + [pivot] + quicksort(height)

list = [3,4,14,1,5,6,7,8,-1,0,1,9,11] # [-1, 0, 1, 1, 3, 4, 5, 6, 7, 8, 9, 11, 14]
print(quicksort(list))

《数据结构与算法图解》中快速排序的实现

使用的也是双指针,实现是双指针递归,强调“分区”,不过选择的基准元素是最后一个元素,相应的指针移动应该需要从左侧先开始(尽管书中并没有强调)

《学习JavaScript数据结构与算法 第三版》中快速排序的实现

双指针递归,将列表的中间值作为了基准元素

堆排序

主要思想:

堆是利用列表进行线性存储是数据结构,其节点之间的关系是通过下标体现的,节点和列表索引的关系是对应的。堆分为大顶堆和小顶堆,也称作最大堆和最小堆,其特点是:大顶堆的任一父节点比其左右子节点的值都大,堆顶元素是最大的;小顶堆的任一父节点比其左右子节点的值都小,堆顶元素是最小的。只关系父子节点之间的大小关系,不关心子节点之间的大小关系。

操作:

构建堆:

将一个列表(任一列表都可以看做是一个堆)调整为符合堆大小的过程称作堆的构建,即可以是调整为大顶堆,也可以是调整为小顶堆。将一个不符合堆的列表构建为一个符合堆的操作无论是构建大顶堆还是小顶堆,都是下沉操作,即将每一个父节点与其子节点进行比较,下沉到合适的位置。

插入与上浮:

在一个最大堆或最小堆末尾添加一个元素使其不满足堆的要求,需要使堆重新满足堆,通常是进行上浮操作,向上与父节点进行比较,放到合适的位置,使堆符合要求。

删除与下沉:

将一个最大堆或最小堆的堆首删除,使其不满足堆的要求,需要使堆重新满足堆,通常是进行下沉操作,将堆末尾节点放置堆首,使其下沉,向下与子节点进行比较,放到合适的位置,使堆符合要求。

判断是否符合最小堆

is_min_heap.py

# 判断一个堆是否符合最小堆
def is_min_heap(arr):
	for i in range(((len(arr)-2)//2),-1,-1):
		parent_index = i
		child_index = i * 2 + 1
		
        # 将child_index赋值为最小的子节点
		if child_index + 1 < len(arr) and arr[child_index+1] < arr[child_index]:
			child_index += 1
		# 逐一检查父节点和子节点之间的关系
		if arr[parent_index] > arr[child_index]:
			return False

	return True

判断是否符合最大堆

is_max_heap.py

# 判断一个堆是否符合最大堆
def is_max_heap(arr):
	for i in range(((len(arr)-2)//2),-1,-1):
		parent_index = i
		child_index = i * 2 + 1
		
        # 将child_index赋值为最大的子节点
		if child_index + 1 < len(arr) and arr[child_index+1] > arr[child_index]:
			child_index += 1
		# 逐一检查父节点和子节点之间的关系
		if arr[parent_index] < arr[child_index]:
			return False

	return True

堆的上浮操作

heap_up.js

# 最大堆的上浮
def head_max_up(child_index,arr):
	child_index = child_index
	parent_index = (child_index - 1) // 2
	temp = arr[child_index]
	while parent_index >= 0:
		# 子节点上浮
		if temp > arr[parent_index]:
             # 单向赋值
			arr[child_index] = arr[parent_index]
             # 定位到上一颗子树
			child_index = parent_index
			parent_index = (child_index - 1) // 2

		if temp < arr[parent_index]:
			break

	arr[child_index] = temp
	return arr

# 最大堆的上浮
def head_min_up(child_index,arr):
	child_index = child_index
	parent_index = (child_index - 1) // 2
	temp = arr[child_index]
	while parent_index >= 0:
		# 子节点上浮
		if temp < arr[parent_index]:
             # 单向赋值
			arr[child_index] = arr[parent_index]
             # 定位到上一颗子树
			child_index = parent_index
			parent_index = (child_index - 1) // 2

		if temp > arr[parent_index]:
			break

	arr[child_index] = temp
	return arr

堆的下沉操作

# 堆的下沉操作
# 最大堆的下沉
def heap_max_down(parent_index,length,arr=[]):
	length = length
	parent_index = parent_index
	child_index = 2*parent_index +1
	tmp=arr[parent_index]

	while child_index < length:
        # 将child_index赋值为最大的子节点
		if child_index+1 < length and arr[child_index+1] > arr[child_index]:
			child_index +=1 
		if tmp > arr[child_index]:
			break
		# 单向赋值
		arr[parent_index] = arr[child_index]
        # 定位到下一颗子树
		parent_index = child_index
		child_index = 2*parent_index +1
		
	arr[parent_index] = tmp
	return arr

# 最小堆的下沉
def heap_min_down(parent_index,length,arr=[]):
	length = length
	parent_index = parent_index
	child_index = 2*parent_index +1
	tmp=arr[parent_index]

	while child_index < length:
        # 将child_index赋值为最小的子节点
		if child_index+1 < length and arr[child_index+1] < arr[child_index]:
			child_index +=1 
		if tmp < arr[child_index]:
			break
		# 单向赋值
		arr[parent_index] = arr[child_index]
        # 定位到下一颗子树
		parent_index = child_index
		child_index = 2*parent_index +1
		
	arr[parent_index] = tmp
	return arr

构建最小堆

heap_min.py

from is_min_heap import is_min_heap
from heap_down import heap_min_down

# 构建小顶堆
def adjustHeap(arr):
    # 遍历每一个非叶子节点
	for i in range(((len(arr)-2) // 2),-1,-1):
        # 将每一个非叶子节点(父节点)下沉到合适的位置
		heap_min_down(i,len(arr),arr)
	return arr

arr = list([4,7,9,1,2,12,-1,5,3,8,6,0,11])
print(adjustHeap(arr)) # [-1, 1, 0, 3, 2, 4, 9, 5, 7, 8, 6, 12, 11]

# 符合最小堆
print(is_min_heap(arr)) # True

# 堆尾添加一个元素,使其不满足最小堆
arr.append(-5)
print(is_min_heap(arr)) # False

构建最大堆

heap_max.py

from is_max_heap import is_max_heap
from heap_down import heap_max_down

# 构建大顶堆
def adjustHeap(arr):
    # 遍历每一个非叶子节点
	for i in range(((len(arr)-2) // 2),-1,-1):
        # 将每一个非叶子节点(父节点)下沉到合适的位置
		heap_max_down(i,len(arr),arr)
	return arr

arr = list([4,7,9,1,2,12,-1,5,3,8,6,0,11])
print(adjustHeap(arr)) # [12, 8, 11, 5, 7, 9, -1, 1, 3, 2, 6, 0, 4]

# 符合最大堆
print(is_max_heap(arr)) # True

# 堆尾添加一个元素,使其不满足最大堆
arr.append(15)
print(is_max_heap(arr)) # False

堆的插入与上浮调整

heap_insert.py

from is_max_heap import is_max_heap
from heap_up import head_max_up

# 14 是新插入的元素
arr = list([12, 8, 11, 5, 7, 9, -1, 1, 3, 2, 6, 0, 4,14])
# 插入元素之后的堆调整 —— 上浮操作
print(head_max_up(len(arr)-1,arr)) # [14, 8, 12, 5, 7, 9, 11, 1, 3, 2, 6, 0, 4, -1]

# 满足大顶堆
print(is_max_heap(arr)) # True

# 插入一个新的元素,使其不满足最大堆
arr.append(19)
# 不满足大顶堆
print(is_max_heap(arr)) # False

堆的删除与下沉

heap_delete.py

from is_max_heap import is_max_heap
from heap_down import heap_max_down
# [14, 8, 12, 5, 7, 9, 11, 1, 3, 2, 6, 0, 4, -1]是最大堆,删除堆首
arr = [14, 8, 12, 5, 7, 9, 11, 1, 3, 2, 6, 0, 4, -1]
print(is_max_heap(arr)) # True

# 删除堆首12,并将堆尾b-1放置在堆首位置
arr[0] = arr[len(arr)-1]
del arr[len(arr)-1] 
print(arr) # [-1, 8, 12, 5, 7, 9, 11, 1, 3, 2, 6, 0, 4]
# 现在不符合最大堆
print(is_max_heap(arr)) # False

# 下沉调整
print(heap_max_down(0,len(arr)-1,arr)) # [12, 8, 11, 5, 7, 9, -1, 1, 3, 2, 6, 0, 4]
# 重新符合最大堆
print(is_max_heap(arr)) # True

堆排序

heap_sort.py

# 堆排序
from heap_down import heap_max_down

def heap_sort(arr=[]):
	# 构建大顶堆
	for i in range(((len(arr)-2) // 2),-1,-1):
		heap_max_down(i,len(arr),arr)

	# 根据大顶堆进行排序(从小到大进行排序)
	for i in range(len(arr)-1,-1,-1):
		# 传入的长度i是关键,隔离已经拍好序的后几个元素,只对前面未排序的进行堆调整,得到最大值(堆首)
		heap_max_down(0,i,arr)
		arr[i],arr[0] = arr[0],arr[i]
	return arr

arr = list([4,7,9,1,2,12,-1,5,3,8,6,0,11])
print(heap_sort(arr)) # [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12]

python中提供的关于堆的操作

# heapqt提供了对应堆的支持,构建的是小顶堆
from heapq import *

my_data = [7,6,3,2,9,0,1,5,4]

# 对my_data应用堆属性,即堆化
heapify(my_data)
print(my_data) # [0, 2, 1, 4, 9, 3, 7, 5, 6]

# 添加一个元素,堆自动调整,继续满足小顶堆
heappush(my_data,0.5)
print(my_data) # [0, 0.5, 1, 4, 2, 3, 7, 5, 6, 9]

# 弹出堆中最小元素(即堆顶元素)
print(heappop(my_data)) # 0
# 堆自动调整
print(my_data) # [0.5, 2, 1, 4, 9, 3, 7, 5, 6]

# 弹出堆顶元素,同时压入一个元素
print(heapreplace(my_data,4.5)) # 0.5
# 将压入的元素上浮到合适位置,继续满足小顶堆
print(my_data) # [1, 2, 3, 4, 9, 4.5, 7, 5, 6]

# 返回堆中最大的n个元素
print(nlargest(3,my_data)) # [9, 7, 6]
# 返回堆中最小的n个元素
print(nsmallest(3,my_data)) # [1, 2, 3]

非交换排序

计数排序

主要思想:使用一个计数列表记录每个值出现的次数,索引和值对应起来,索引的数值代表和以索引为值的元素的数量。索引本身是有序的,因此并不需要比较大小。

缺点:(1)不是稳定排序;(2)只能对整数进行排序。

改进:可以使用计数列表元素的累加和表示元素出现的顺序,这样即使有重复元素,只要将待排列表倒着进行遍历,即可对应到元素应该排列的索引位置。

不稳定的计数排序

count_sort.py

# 不稳定计数排序
def count_sort(arr):
	max_num = max(arr)
	min_nun = min(arr)
	space = max_num - min_nun
	# 初始化计数列表,用0进行填充
	countArr = [0] * (space+1)
	# 结果容器
	resultArr = []
	
	# 计数
	for num in arr:
		for j in range(0,space+1):
			if num == j:
				countArr[j]+=1
	print(countArr) # [1, 1, 1, 1, 2, 2, 2, 1, 1, 0, 1]

	# 排序			
	for i in range(0,len(countArr)):
		if countArr[i] > 0:
			tmp = countArr[i]
			while tmp:
				resultArr.append(min_nun+i)
				tmp-=1

	return resultArr

my_array = list([4, 4, 6, 5, 3, 2, 8, 1, 7, 5, 6, 0, 10])
print(count_sort(my_array))

稳定的计数排序

count_sort_pro.py

# 稳定计数排序
def count_sort(arr):
	max_num = max(arr)
	min_nun = min(arr)
	space = max_num - min_nun
	# 初始化计数列表,用0进行填充
	countArr = [0] * (space+1)
	# 累加列表
	accArr = [0] * (space+1)
	# 结果容器
	sorted_array = [0] * len(arr)
	
	# 计数
	for num in arr:
		for j in range(0,space+1):
			if num == j:
				countArr[j]+=1
	print(countArr) # [1, 1, 1, 1, 2, 2, 2, 1, 1, 0, 1]

	# 累加
	acculate = 0
	for i in range(0,space+1):
		acculate+=countArr[i]
		accArr[i] += acculate

	print(accArr) # [1, 2, 3, 4, 6, 8, 10, 11, 12, 12, 13]

	# 为了保证顺序,从后往前确定元素位置
	for i in range(len(arr)-1,-1,-1):
		# 这里的arr[i]-min_nun是偏移量,accArr[arr[i]-min_nun]是在累加列表中的值,减1是因为和实际元素位置相差1
		sorted_array[accArr[arr[i]-min_nun]-1] = arr[i]
		accArr[arr[i]-min_nun] -= 1

	return sorted_array

my_array = list([4, 4, 6, 5, 3, 2, 8, 1, 7, 5, 6, 0, 10])
print(count_sort(my_array)) # [0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 7, 8, 10]

桶排序

主要思想:准备合适数量的桶,每个桶按照相同的大小放置元素大小在桶区间的元素。将列表中的元素按照一定的间隔放在相应的桶内,再对桶内的元素进行稳定排序。

bucket_sort.py

# 导入堆排序
from heap_sort import heap_sort

def bucket_sort(arr):
	max_num = max(arr)
	min_nun = min(arr)
	space = max_num - min_nun
	# 桶的数量
	bucket_num = len(arr);
	# 创建二维列表(桶)
	bucket_arr = [[]*bucket_num for _ in range(bucket_num)]
	# 确定桶的区间
	bucket_range = space / (bucket_num-1)
	# 结果容器
	result = []

	# 元素入桶
	for i in range(0,len(arr)):
		# 桶的编号
		bucket_index = int((arr[i]-min_nun) // bucket_range)
		# 放到对应桶内
		bucket_arr[bucket_index].append(arr[i])
	print(bucket_arr) # [[0.14, 0.54], [1.36, 1.11, 1.12, 1.13, 1.37], [1.78], [2.56], [], [3.57, 3.57], [4.23], [], [], [], [6.78], [], [], [8.77]]

	# 桶内使用堆稳定排序
	for arr in bucket_arr:
		heap_sort(arr)
		for i in arr:
			result.append(i)
	return result

arr = [3.57,4.23,1.36,8.77,1.11,6.78,1.12,1.13,1.37,2.56,3.57,1.78,0.14,0.54]

print(bucket_sort(arr)) # [0.14, 0.54, 1.11, 1.12, 1.13, 1.36, 1.37, 1.78, 2.56, 3.57, 3.57, 4.23, 6.78, 8.77]