数据结构——基于python

数据结构

  • 数组和链表是物理结构
  • 顺序表、栈、队列、树、图是逻辑结构

python实现

数组

  • list内置类

  • list可以是任何对象

  • 常见函数

  • 增加元素

list.append(obj) #尾部增加元素
####例子
x = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']
x.append('Thursday')
print(x)
# ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Thursday']


list.insert(index, obj) #在编号 index 位置前插入 obj 
####例子
x = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']
x.insert(2, 'Sunday')
print(x)
# ['Monday', 'Tuesday', 'Sunday', 'Wednesday', 'Thursday', 'Friday']



list.extend(seq)  #在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表)
  • 删除元素
list.remove(obj)  #移除列表中某个值的第一个匹配项
####例子
x = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']
x.remove('Monday')
print(x) # ['Tuesday', 'Wednesday', 'Thursday', 'Friday']



list.pop([index=-1])  #移除列表中的一个元素(默认最后一个元素),并且返回该元素的值
####例子
x = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']
y = x.pop()
print(y) # Friday



del #已知位置,删除后不能返回使用
x = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']
del x[0:2]
print(x) # ['Wednesday', 'Thursday', 'Friday']

  • 查找、修改
# 直接使用索引值进行查找修改
week = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']
print(len(week))
week[0]=1
week[2]=2
print(week)

'''
5
[1, 'Tuesday', 2, 'Thursday', 'Friday']
'''

链表

在这里插入图片描述
  • # 单链表
    class Node(object):
        def __init__(self, data):
            # 数据域
            self.data = data
            # 向后的引用域
            self.next = None
    
    
    class SingleLinkList(object):
        def __init__(self):
            self.head = None
    
        # is_empty() 链表是否为空
        def is_empty(self):
            return not self.head
    
        # add(data) 链表头部添加元素
        # O(1)
        def add(self, data):
            node = Node(data)
            node.next = self.head
            self.head = node
    
        # show() 遍历整个链表
        # O(n)
        def show(self):
            cur = self.head
            while cur != None:
                # cur是一个有效的节点
                print(cur.data, end=' --> ')
                cur = cur.next
            print()
    
        # append(item) 链表尾部添加元素
        # O(n)
        def append(self, data):
            cur = self.head
            while cur.next != None:
                cur = cur.next
            # cur指向的就是尾部节点
            node = Node(data)
            cur.next = node
    
        # length() 链表长度
        # O(n)
        def length(self):
            count = 0
            cur = self.head
            while cur != None:
                # cur是一个有效的节点
                count += 1
                cur = cur.next
            return count
    
        # search(item) 查找节点是否存在
        # O(n)
        def search(self, data):
            cur = self.head
            while cur != None:
                if cur.data == data:
                    return True
                cur = cur.next
            return False
    
        # remove(data) 删除节点
        # O(n)
        def remove(self, data):
            cur = self.head
            pre = None
            while cur != None:
                if cur.data == data:
                    if cur == self.head:
                        self.head = self.head.next
                        return
                    pre.next = cur.next
                    return
                pre = cur
                cur = cur.next
    
        # insert(index, data) 指定位置添加元素
        # O(n)
        def insert(self, index, data):
            if index <= 0:
                self.add(data)
                return
            if index > self.length() - 1:
                self.append(data)
                return
            cur = self.head
            for i in range(index-1):
                cur = cur.next
            # cur指向的是第index节点第前置节点
            node = Node(data)
            node.next = cur.next
            cur.next = node
    
    
  • # 双链表
    class Node(object):
        #节点的类
        def __init__(self,item):
            self.item = item
            self.prev = None
            self.next = None
    
    class DLinkList(object):
        #双向链表的类
        def __init__(self):
            #指向链表的头节点
            self.head = None
    
        def is_empty(self):
            #链表是否为空
            return self.head == None
    
    
        def length(self):
            #链表长度
            cur = self.head
            #计数器
            count = 0
            while cur != None:
                count += 1
                cur = cur.next
            return count
    
        def travel(self):
            #遍历链表
            cur = self.head
            while cur != None:
                print(cur.item)
                cur = cur.next
    
        def add(self,item):
            #链表头部添加
            node = Node(item)
            if self.is_empty():
                #如果是空链表,将head指向node
                #给链表添加第一个元素
                self.head = node
            else:
                #如果链表不为空,在新的节点和原来的首节点之间建立双向链接
                node.next = self.head
                self.head.prev = node
                #让head指向链表的新的首节点
                self.head = node
    
    
        def append(self,item):
            #链表尾部添加
            #创建新的节点
            node = Node(item)
            if self.is_empty():
                #空链表,
                self.head = node
            else:
                #链表不为空
                cur = self.head
                while cur.next != None:
                    cur = cur.next
                #cur的下一个节点是node
                cur.next = node
                #node的上一个节点是
                node.prev = cur
    
        def insert(self,pos,item):
            #指定位置添加
            if pos <=0:
                self.add(item)
            elif pos > self.length()-1:
                self.append()
            else:
                node = Node(item)
                cur = self.head
                count = 0
                #把cur移动到指定位置的前一个位置
                while count < (pos - 1):
                    count+=1
                    cur = cur.next
                #node的prev指向cur
                node.prev = cur
                #node的next指向cur的next
                node.next = cur.next
                cur.next.prev = node
                cur.next = node
    
        def remove(self,item):
            #删除节点
            if self.is_empty():
                return
            else:
                cur = self._head
                if cur.item == item:
                    #首节点是要删除的节点
                    if cur.next == None:
                        #说明链表中只有一个节点
                        self.head = None
                    else:
                        #链表多于一个节点的情况
                        cur.next.prev = None
                        self.head = cur.next
                else:
                    # 首节点不是要删除的节点
                    while cur != None:
                        if cur.item == item:
                            cur.prev.next = cur.next
                            cur.next.prev = cur.prev
                            break
                        cur = cur.next
    
    
        def search(self,item):
            #查找节点是否存在
            cur = self._head
            while cur != None:
                if cur.item == item:
                    return True
                cur = cur.next
            return False
    
    

  • 直接使用list创建stack即可

  • >>> stack=[]
    >>> stack.append(1)
    >>> stack.append(2)
    >>> stack.append(3)
    >>> stack
    [1, 2, 3]
    >>> stack.pop()   # 弹出栈顶元素
    3
    >>> stack.pop()
    2
    >>> stack
    [1]
    >>> stack.pop()
    1
    >>> stack
    []
    

队列

  • 单向队列
import queue
q = queue.Queue()
# 写
for i in range(5):
    q.put(i)
# 读
while not q.empty():
    print(q.get())
print(q.empty())


####
0
1
2
3
4
True
####
  • 双向队列
    • deque.append
    • deque.appendleft
    • deque.pop
    • deque.popleft
from collections import deque
queue=deque()
queue.append('a')
queue.append('b')
queue.append('c')
print(queue)
queue.appendleft('A')
queue.appendleft('B')
print(queue)
print(queue.pop())
print(queue.popleft())
print(queue)


####
deque(['a', 'b', 'c'])
deque(['B', 'A', 'a', 'b', 'c'])
c
B
deque(['A', 'a', 'b'])
####

哈希表——dict字典

  • 数值、字符和元组 都能被哈希,因此它们是不可变类型。

  • 列表、集合、字典不能被哈希,因此它是可变类型。

  • 用 hash(X) ,只要不报错,证明 X 可被哈希,即不可变,反过来不可被哈希,即可变。

  • 字典以"关键字"为索引,关键字可以是任意不可变类型,通常用字符串或数值。

  • 字典 是无序的 键:值( key:value )对集合,键必须是互不相同的(在同一个字典之内)。

    1. dict 内部存放的顺序和 key 放入的顺序是没有关系的。
    2. dict 查找和插入的速度极快,不会随着 key 的增加而增加,但是需要占用大量的内存。
      字典 定义语法为 {元素1, 元素2, …, 元素n}
    3. 其中每一个元素是一个「键值对」-- 键:值 ( key:value )
    4. 关键点是「大括号 {}」,「逗号 ,」和「冒号 :」
    5. 大括号 – 把所有元素绑在一起
    6. 逗号 – 将每个键值对分开
    7. 冒号 – 将键和值分开
  • dic = dict()
    dic['a'] = 1
    dic['b'] = 2
    dic['c'] = 3
    print(dic)
    # {'a': 1, 'b': 2, 'c': 3}
    

二叉树的前序、中序、后序遍历(深度优先遍历)

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


root= TreeNode(5)
root.left=TreeNode(7)
root.right=TreeNode(8)
root.left.left=TreeNode(3)
root.left.right=TreeNode(4)
root.right.left=TreeNode(6)

# 递归遍历
def pre_order_traversal(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):
    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):
    if node is None:
        return
    post_order_traversal(node.left)
    post_order_traversal(node.right)
    print(node.data)
    return node


# 非递归遍历
# 前序,使用栈数据结构,都具备回溯功能
def pre_order_traversal_with_stack(node):
    if node is None:
        return
    stack=[node]
    while len(stack)>0:
        node=stack.pop()
        # 前序,先打印
        print(node.data)
        # 栈的弹出顺序与入栈顺序相反 因此先入右再入左
        if node.right:
            stack.append(node.right)
        if node.left:
            stack.append(node.left)

# 中序遍历
def in_order_traversal_with_stack(node):
    if node is None:
        return
    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(node):
    # 后序遍历二叉树非递归 后序遍历是 左-右-中
    # 反过来就是 中-右-左 其实就是前序遍历镜像二叉树(即左右互换)
    # 利用两个栈
    if node is None:
        return
    stack=[node]
    result=[]
    while len(stack)>0:
        node=stack.pop()
        # 结果入栈
        result.append(node.data)
        # 镜像前序二叉树,栈的弹出顺序与入栈顺序相反 因此先入左再入右
        if node.left:
            stack.append(node.left)
        if node.right:
            stack.append(node.right)
    while len(result)>0:
        print(result.pop())

        
        
def post_order_traversal_with_flag(node):
    # 后序遍历跟前序和中序不同的地方在于,访问完左子树后,会回溯到父节点,此时不能访问父节点,访问完右子树后,回溯到父节点,
    # 此时才能访问父节点。所以回溯到父节点的时候,要判断上一次操作访问的是哪里,
    # 如果上一次操作访问的时左子树,那么不可访问当前节点,得先去访问当前节点的右子树,
    # 如果上一次操作访问的是右子树,那么可以访问当前节点。
    if node is None:
        return
    stack=[]
    preVisited =None #访问标志
    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[-1]# 读取最后一个节点,先不弹出
            # 右节点不存在或已经被访问过了
            if node.right is None or node.right==preVisited:
                print(node.data)
                stack.pop()
                preVisited=node
                node=None #防止重复访问,避免进入循环
            else:
                node=node.right



# pre_order_traversal(root)
# print('\n非递归')
# pre_order_traversal_with_stack(root)

# in_order_traversal(root)
# print('\n非递归')
# in_order_traversal_with_stack(root)

post_order_traversal(root)
print('\n')
post_order_traversal_with_stack(root)
print('\n')
post_order_traversal_with_flag(root)

二叉树的层序遍历(广度优先遍历)

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

root= TreeNode(5)
root.left=TreeNode(7)
root.right=TreeNode(8)
root.left.left=TreeNode(3)
root.left.right=TreeNode(4)
root.right.left=TreeNode(6)
root.right.right=TreeNode(22)
root.left.right.left=TreeNode(33)

# 广度优先遍历——层序遍历
# 非递归思路:采用队列queue,出队一个根节点,入队两个子结点
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)


# 递归思路:依次打印出每一层,输入根节点和高度(从1开始)
def level_order_traversal_recurrence(node,level):
    if node is None or level==0:
        return
    if level==1:
        print(node.data)
    level_order_traversal_recurrence(node.left,level-1)
    level_order_traversal_recurrence(node.right,level-1)
# 高度为4
for i in range(1,5):
    level_order_traversal_recurrence(root,i)

print('\n非递归')
level_order_traversal(root)

'''
#####结果
5
7
8
3
4
6
22
33

非递归
5
7
8
3
4
6
22
33
#####
'''

二叉堆

  • 二叉堆是一个完全二叉树,储存方式为顺序存储,即存储在数组里,python里面即列表list

  • 如何定位左右孩子,依靠数组下标实现,从0开始,左孩子下标为2*parent+1,右孩子为2*parent+2。

  • 最大堆:任何一个父节点的值,都大于或等于左孩子和右孩子的节点的值,堆顶为最大值

  • 最小堆:任何一个父节点的值,都小于或等于左孩子和右孩子的节点的值,堆顶为最小值

  • 三种操作:

    • 尾结点插入——上浮
    • 堆顶删除——下沉
    • 二叉堆的构建(依次从最大非叶子节点下沉)
# 以最小堆为例

# 二叉堆的插入,尾结点上浮操作,以最小堆为例
def up_adjust(array=[]):
    child_index = len(array) - 1  # 下标从0开始
    parent_index = (child_index - 1) // 2  # 除以2,向下取整
    temp = array[child_index]
    while child_index > 0 and temp < array[parent_index]:
        # temp一直存在比较,只需要单向赋值
        array[child_index] = array[parent_index]
        child_index = parent_index
        parent_index = (child_index - 1) // 2  # 除以2,向下取整
    array[child_index] = temp


# 二叉堆的删除,堆顶下沉操作,删除堆顶,尾结点放在堆顶上,以最小堆为例
def down_adjust(array=[]):
    tem = array.pop()
    array[0] = tem
    length = len(array)
    parent_index = 0
    child_index = 2 * parent_index + 1  # 左孩子下标
    while child_index < length:
        # 存在右孩子且比左孩子小
        if child_index + 1 < length and array[child_index + 1] < array[child_index]:
            child_index += 1
        # 父节点的值最小,直接返回
        if tem <= array[child_index]:
            break
        # 直接单向赋值
        array[parent_index] = array[child_index]
        parent_index = child_index
        child_index = 2 * parent_index + 1
    array[parent_index] = tem


# 二叉堆的下沉函数,用于创建二叉堆的迭代过程,最小堆为例
def down_for_build(parent_index, array=[]):
    tem = array[parent_index]
    child_index = parent_index * 2 + 1
    length = len(array)
    while child_index < length:
        # 存在右孩子且比左孩子小
        if child_index + 1 < length and array[child_index + 1] < array[child_index]:
            child_index += 1
        # 父节点的值最小,直接返回
        if tem <= array[child_index]:
            break
        # 直接单向赋值
        array[parent_index] = array[child_index]
        parent_index = child_index
        child_index = 2 * parent_index + 1
    array[parent_index] = tem


# 二叉堆的创建过程,最小堆的为例:
def build_heap(array=[]):
    # 列表下标为0开始,所以要多减一个1,即减2,从最后一个非叶子节点开始
    for i in range((len(array) - 2) // 2, -1, -1):
        down_for_build(i, array)

myarray = list([1, 3, 2, 6, 5, 7, 8, 9, 10,0])
up_adjust(myarray)
print(myarray)

myarray1 = list([1, 3, 2, 6, 5, 7, 8, 9, 10])
down_adjust(myarray1)
print(myarray1)

myarray2=list([7,1,3,10,5,2,8,9,6])
build_heap(myarray2)
print(myarray2)


####输出
'''
[0, 1, 2, 6, 3, 7, 8, 9, 10, 5]
[2, 3, 7, 6, 5, 10, 8, 9]
[1, 5, 2, 6, 7, 3, 8, 9, 10]

'''

优先队列

  • 二叉堆的变形
  • 最大优先队列:无论入队顺序如何,当前最大元素都会优先出队,基于最大堆实现的
  • 最小优先队列:无论入队顺序如何,当前最小元素都会优先出队,基于最小堆实现的
  • 入队:即二叉堆的尾结点上浮
  • 出队:即二叉堆的堆顶删除,尾结点替代堆顶,然后下沉操作

排序算法

  • 主流十大排序,分成三类
    • 时间复杂度为O(N2)的排序算法:
      • 冒泡排序——稳定
      • 选择排序——不稳定
      • 插入排序——稳定
      • 希尔排序(性能略优于O(N2),但比不上O(nlogn))——不稳定
    • 时间复杂度为O(nlogn))的排序算法:
      • 快速排序——不稳定
      • 归并排序——稳定
      • 堆排序——不稳定
    • 时间复杂度为线性的排序算法:(均为非比较排序)
      • 计数排序——稳定
      • 桶排序——稳定
      • 基数排序——稳定
  • 稳定排序和不稳定排序:值相同的元素排序前后位置是否改变判定

冒泡排序

  • 最简单的冒泡排序:思路,两层for循环
# 冒泡算法
def bubble_sort_v1(array=[]):
    # 注意减1,避免溢出读取位置
    for i in range(len(array) - 1):
        for j in range(len(array) - i - 1):
            if array[j] > array[j + 1]:
                tem = array[j]
                array[j] = array[j + 1]
                array[j + 1] = tem

  • 冒泡算法优化1:
    • 思路:利用标记判断是否已经有序,是的话退出循环
# 冒泡算法优化1:判断数列已经有序,直接退出,利用标记
def bubble_sort_v2(array=[]):
    for i in range(len(array) - 1):
        # 是否有序的标记,初始化为true
        is_sorted = True
        for j in range(len(array) - i - 1):
            if array[j] > array[j + 1]:
                tem = array[j]
                array[j] = array[j + 1]
                array[j + 1] = tem
                # 有元素交换,并非有序
                is_sorted = False
        if is_sorted:
            # 仍为真,说明已经有序,提前退出
            break
  • 冒泡算法优化2:设立有序边界
    • 思路:每次交换后的index保存,在下一次大循环的时候重新设立截止边界
# 冒泡排序优化2:有序边界可能不止是1、2、3...,实际可能比更多,每一步更新有序边界即为优化的点
def bubble_sort_v3(array=[]):
    # 更新的边界
    last_exchange_index = len(array) - 1
    # 初始化有序边界
    sort_border = last_exchange_index
    for i in range(len(array) - 1):
        # 是否有序的标记,初始化为true
        is_sorted = True
        for j in range(sort_border):
            if array[j] > array[j + 1]:
                tem = array[j]
                array[j] = array[j + 1]
                array[j + 1] = tem
                # 更新有序标记
                is_sorted = False
                # 更新有序边界
                last_exchange_index = j
        if is_sorted:
            break
        sort_border = last_exchange_index
  • 鸡尾酒排序
    • 冒泡排序的进阶
    • 思路:左右进行排序,初始设置的有序边界都是i(即递增的0,1,2…)
    • 适用于内部很多已经排好序的数组
# 鸡尾酒排序,冒泡排序的进阶,适用大部分已经排好序的数组
def cock_tail_sort(array=[]):
    # 注意向下取整
    for i in range(len(array) // 2):
        # 奇数轮,从左往右
        # 是否有序的标记,初始化为true
        is_sorted = True
        for j in range(i, len(array) - i - 1):
            if array[j] > array[j + 1]:
                tem = array[j]
                array[j] = array[j + 1]
                array[j + 1] = tem
                # 有元素交换,并非有序
                is_sorted = False
        if is_sorted:
            # 仍为真,说明已经有序,提前退出
            break

        # 偶数轮,从右往左
        is_sorted = True
        # 最后一个已经排好序了,多减1变为减2
        for j in range(len(array) - i - 2, i, -1):
            if array[j] < array[j - 1]:
                tem = array[j]
                array[j] = array[j - 1]
                array[j - 1] = tem
                # 有元素交换,并非有序
                is_sorted = False
        if is_sorted:
            # 仍为真,说明已经有序,提前退出
            break
  • 鸡尾酒排序的进阶:增加有序边界
# 鸡尾酒排序优化,加入有序边界
def cock_tail_sort_v2(array=[]):
    # 奇数轮有序边界
    last_exchange_index_left = len(array) - 1
    # 初始化奇数轮有序边界
    sort_border_left = last_exchange_index_left
    # 偶数轮有序边界
    last_exchange_index_right = 0
    # 初始化奇数轮有序边界
    sort_border_right = last_exchange_index_right
    # 注意向下取整
    for i in range(len(array) // 2):
        # 奇数轮,从左往右
        # 是否有序的标记,初始化为true
        is_sorted = True
        for j in range(i, sort_border_left):
            if array[j] > array[j + 1]:
                tem = array[j]
                array[j] = array[j + 1]
                array[j + 1] = tem
                # 有元素交换,并非有序
                is_sorted = False
                # 更新有序边界
                last_exchange_index_left = j
        if is_sorted:
            # 仍为真,说明已经有序,提前退出
            break
        sort_border_left = last_exchange_index_left

        # 偶数轮,从右往左
        is_sorted = True
        # 最后一个已经排好序了,多减1变为减2
        for j in range(len(array) - i - 2, sort_border_right, -1):
            if array[j] < array[j - 1]:
                tem = array[j]
                array[j] = array[j - 1]
                array[j - 1] = tem
                # 有元素交换,并非有序
                is_sorted = False
                # 更新有序边界
                last_exchange_index_right = j
        if is_sorted:
            # 仍为真,说明已经有序,提前退出
            break
        sort_border_right = last_exchange_index_right


myarray2 = list([8, 2, 3, 4, 5, 6, 7, 15, 1])
cock_tail_sort_v2(myarray2)
print(myarray2)

'''
[1, 2, 3, 4, 5, 6, 7, 8, 15]
'''

选择排序

  • 算法思想:
    • A.在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
    • B.从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾
    • C.以此类推,直到所有元素均排序完毕
  • 与冒泡排序不同的是,选择排序是先找到最小值,然后交换位置。冒泡排序是每一次都交换,最后一定是最小值。
# 选择排序,按最大排序
def select_sort(array=[]):
    for i in range(0,len(array) - 1):
        flag = i
        for j in range(i+1, len(array) , 1):
            if (array[j] > array[flag]):
                flag = j
        tem = array[i]
        array[i] = array[flag]
        array[flag] = tem


myarray1 = list([-1.2, 4.6, 3.5, 4.4, 5.5, 6.8, 7.9, 15.2, 1.1, 16.4])
select_sort(myarray1)
print(myarray1)


'''
[16.4, 15.2, 7.9, 6.8, 5.5, 4.6, 4.4, 3.5, 1.1, -1.2]
'''

插入排序

  • 它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
  • 和冒泡排序的区别在于,冒泡排序是拿一个元素和无序序列去遍历比较,比较得到整个无序序列中最值,然后放入有序序列,而一旦放入有序序列,就不再碰了。
  • 插入排序是拿一个元素和有序的数列去比,从单一元素的假有序一个一个加,拿一个元素进入有序序列,碰到比自己大或小的就即刻坐下,不再继续比较,插入排序的当前元素是无论如何不会触摸无序序列的。
# 插入排序
def insertion_sort(array=[]):
    for i in range(1,len(array),1):
        # 是否进入有序序列
        if(array[i]<array[i-1]):
            tem=array[i]
            flag=i-1
            for j in range(i-1,-1,-1):
                # 开辟空间
                if array[j]>tem:
                    array[j + 1] = array[j]
                    flag=j
            array[flag]=tem


myarray1 = list([-1.2, 4.6, 3.5, 4.4, 5.5, 6.8, 7.9, 15.2, 1.1, 16.4])
insertion_sort(myarray1)
print(myarray1)

'''
[-1.2, 1.1, 3.5, 4.4, 4.6, 5.5, 6.8, 7.9, 15.2, 16.4]
'''


# 或者使用while循环
# 插入排序
def insertion_sort(array=[]):
    for i in range(1, len(array), 1):
        # 是否进入有序序列
        if (array[i] < array[i - 1]):
            tem = array[i]
            flag = i - 1
            while flag >= 0 and array[flag] > tem:
                array[flag + 1] = array[flag]
                flag -= 1
            array[flag+1] = tem


myarray1 = list([-1.2, 4.6, 3.5, 4.4, 5.5, 6.8, 7.9, 15.2, 1.1, 16.4])
insertion_sort(myarray1)
print(myarray1)


# 以下更简洁,同时衔接希尔排序
# 插入排序
def insertion_sort(array=[]):
    #gap=1
    for i in range(1, len(array), 1):
        tem = array[i]
        flag = i
        while flag >= 1 and array[flag-1] > tem:
            array[flag] = array[flag-1]
            flag -= 1
        array[flag] = tem


希尔排序

  • 希尔排序(Shell’s Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。
  • 分组依次插入排序,设置依次整除2
# 希尔排序
def shell_sort(array=[]):
    length=len(array)
    # 选择增量
    gap=length//2
    while gap:
        # gap即第二个元素,所以是分组插入排序
        for i in range(gap,length,1):
            tem=array[i]
            j=i
            while j>=gap and tem<array[j-gap]:
                array[j]=array[j-gap]
                #以下是分组的间隔
                j-=gap
            array[j]=tem
        gap=gap//2



myarray1 = list([-1.2, 4.6, 3.5, 4.4, 5.5, 6.8, 7.9, 15.2, 1.1, 16.4])
shell_sort(myarray1)
print(myarray1)

快速排序

  • 思路:分治法

  • 基准元素的选择:一般选择第一个元素,也可以随机选取之后和第一个元素调换,避免极端情况

  • 双边循环递归法查找分界索引位置

    • 使用双指针
# 快速排序
# 思路:分治法,双边循环递归
def quick_sort(start_index,end_index,array=[]):
    # 递归结束条件:start_index>=end_index
    if start_index>=end_index:
        return
    #获取基准元素
    pivot_index=partition_v1(start_index,end_index,array)
    # 根据基准元素,分成两部分进行递归
    quick_sort(start_index,pivot_index-1,array)
    quick_sort(pivot_index+1,end_index,array)

def partition_v1(start_index,end_index,array=[]):
    #取第一个元素为基准元素
    pivot=array[start_index]
    #循环指针
    left=start_index
    right=end_index
    while left!=right:
        #控制right指针左移,大于等于基准元素才左移,此时是正确的顺序
        while left<right and array[right]>=pivot:
            right-=1
        # 控制left指针右移,小于等于基准元素才左移,此时是正确的顺序
        while left<right and array[left]<=pivot:
            left+=1
        # 交换元素
        if left<right:
            tem=array[left]
            array[left]=array[right]
            array[right]=tem

    # 指针重合,返回分区位置
    array[start_index]=array[left]
    array[left]=pivot
    return left

  • 单边循环递归法
    • 使用mark标志,左边比基准元素小,右边比基准元素大
# 快速排序
# 思路:分治法,单边循环递归
def quick_sort_v2(start_index, end_index, array=[]):
    # 递归结束条件:start_index>=end_index
    if start_index >= end_index:
        return
    # 获取基准元素
    pivot_index = partition_v2(start_index, end_index, array)
    # 根据基准元素,分成两部分进行递归
    quick_sort(start_index, pivot_index - 1, array)
    quick_sort(pivot_index + 1, end_index, array)


def partition_v2(start_index, end_index, array):
    # 确定基准元素
    pivot = array[start_index]
    mark = start_index
    # 所有都要遍历,所以end_index+1
    for i in range(start_index + 1, end_index + 1):
        if array[i] < pivot:
            mark += 1
            if i != mark:
                tem = array[mark]
                array[mark] = array[i]
                array[i] = tem
    array[start_index] = array[mark]
    array[mark] = pivot
    return mark

  • 非递归方法
    • 使用栈
    • 输入的是哈希表,包含读取的头和尾
# 快速排序
# 思路:分治法,非递归,使用栈,栈中元素为哈希
def quick_sort_v3(start_index, end_index, array=[]):
    stack = []
    root_param = {"start_index": start_index, "end_index": end_index}
    stack.append(root_param)
    # 循环结束条件,栈为空
    while len(stack) > 0:
        # 栈顶元素出栈,得到开始停止的下标
        param = stack.pop()
        pivot = partition_v3(param.get("start_index"), param.get("end_index"), array)
        # 根据返回基准位置,划分左右哈希入栈
        if param.get("start_index") < (pivot - 1):
            left_param = {"start_index": start_index, "end_index": pivot - 1}
            stack.append(left_param)
        if param.get("end_index") > (pivot + 1):
            right_param = {"start_index": pivot + 1, "end_index": end_index}
            stack.append(right_param)


def partition_v3(start_index, end_index, array):
    # 确定基准元素
    pivot = array[start_index]
    mark = start_index
    # 所有都要遍历,所以end_index+1
    for i in range(start_index + 1, end_index + 1):
        if array[i] < pivot:
            mark += 1
            if i != mark:
                tem = array[mark]
                array[mark] = array[i]
                array[i] = tem
    array[start_index] = array[mark]
    array[mark] = pivot
    return mark

归并排序

  • 思路:分治法
  • 先分解,后归并
  • 采用递归方式
def merge_sort(array=[]):
    sort(0, len(array) - 1, array)

def sort(L, R, array=[]):
    if L == R:
        return
    mid = L + (R - L) // 2
    # 分解
    sort(L, mid, array)
    sort(mid + 1, R, array)
    # 归并
    merge(L, mid, R, array)


def merge(L, mid, R, array=[]):
    tem = [0] * (R - L + 1)
    # 比较两部分元素,填入缓存数组,或者用三端while也可以,不用for + if
    p_left = L
    p_right = mid + 1
    for i in range(0, (R - L + 1), 1):
        if p_left <= mid and p_right <= R:
            if array[p_left] < array[p_right]:
                tem[i] = array[p_left]
                p_left += 1
            else:
                tem[i] = array[p_right]
                p_right += 1
        elif p_left<=mid:
            tem[i] = array[p_left]
            p_left += 1
        elif p_right<=R:
            tem[i] = array[p_right]
            p_right += 1

    # 返回原数组
    for i in range(len(tem)):
        array[L + i] = tem[i]


myarray1 = list([-1.2, 4.6, 3.5, 4.4, 5.5, 6.8, 7.9, 15.2, 1.1, 16.4])
merge_sort(myarray1)
print(myarray1)

堆排序

  • 无序数组构建二叉堆
    • 从小到大排序,用最大堆
    • 从大到小排序,用最小堆
  • 循环删除堆顶元素,替换到二叉堆的末尾,堆顶下沉产生新的堆顶。
  • 注意点,二叉堆的创建函数,输入参数要多加一个长度,避免array[:i]创建了新的数组输入,没有在原数组上更改
# 堆排序
# 二叉堆的下沉函数,用于创建二叉堆的迭代过程,最小堆为例
def down_for_build(parent_index, length, array=[]):
    tem = array[parent_index]
    child_index = parent_index * 2 + 1
    # length = len(array)
    while child_index < length:
        # 存在右孩子且比左孩子小
        if child_index + 1 < length and array[child_index + 1] < array[child_index]:
            child_index += 1
        # 父节点的值最小,直接返回
        if tem <= array[child_index]:
            break
        # 直接单向赋值
        array[parent_index] = array[child_index]
        parent_index = child_index
        child_index = 2 * parent_index + 1
    array[parent_index] = tem


def heap_sort(array=[]):
    # 创建最小二叉堆
    # 列表下标为0开始,所以要多减一个1,即减2,从最后一个非叶子节点开始,因为2*x+1等于左孩子
    for i in range((len(array) - 2) // 2, -1, -1):
        down_for_build(i, len(array), array)

    # 循环交换尾部元素到堆顶,并调节堆产生新的堆顶
    for i in range(len(array) - 1, 0, -1):
        tem1 = array[i]
        array[i] = array[0]
        array[0] = tem1
        # 调节最小堆
        down_for_build(0, i, array)

计数排序

  • 是一种非比较排序,是按数组下标来进行排序
  • 步骤:(以下是优化版本,得出的排序是一种稳定排序)
    • 求数组最大最小值,得出数组长度max-min+1
    • 创建统计数组,统计对应元素个数,注意min作为偏移量
    • 统计数组变形,后面元素等于前面元素之和
    • 倒序遍历原始数组,从统计数组找到正确位置,输出排序数组
# 计数排序
def count_sort(array=[]):
    # 1.得出最大最小值
    max_value = max(array)
    print(max_value)
    min_value = min(array)
    print(min_value)
    d = max_value - min_value + 1
    # 2.创建统计数组并统计对应元素个数,注意偏移量
    count_array = [0] * d
    for i in range(0, len(array)):
        count_array[array[i] - min_value] += 1
    # 3.统计数组变形,后面元素等于前面元素之和
    for i in range(1, len(count_array)):
        count_array[i] += count_array[i - 1]
    # 4.倒序遍历原始数组,从统计数组找到正确位置,输出排序数组
    sort_array = [0] * len(array)
    for i in range(len(array) - 1, -1, -1):
        # 减1是因为数组从0开始
        sort_array[count_array[array[i] - min_value] - 1] = array[i]
        # 位置减1,保证排序稳定性
        count_array[array[i] - min_value] -= 1
    return sort_array


myarray1 = list([8, 2, 3, 4, 5, 6, 7, 15, 1, 16])
myarray2=count_sort(myarray1)
print(myarray2)


'''
16
1
[1, 2, 3, 4, 5, 6, 7, 8, 15, 16]
'''

桶排序

  • 可以进行小数的排序,克服计数排序只能进行整数排序的缺点
  • 重点在于标号的计算,向下取整得出桶的序号
  • 我们这种分法:
    • 桶的数量等于原数组元素的数量
    • 最后一个桶只有最大值
    • 区间跨度=(max-min)/(桶的数量-1)
    • 例如
      • [0.5,1.5), [1.5,2.5), [2.5,3.5), [3.5,4.5), [4.5,4.5]
# 桶排序
def bucket_sort(array=[]):
    # 1.计算最大最小值,得出差值d
    max_value = max(array)
    min_value = min(array)
    d = max_value - min_value
    # 2.初始化桶,列表的列表
    bucket_num = len(array)
    bucket_list = []
    for i in range(0, bucket_num):
        bucket_list.append([])  # 子集为列表
    # 3.遍历原始数组,将每个元素放入桶中,利用向下取整,math.floor或者int()都行
    for i in range(0, len(array)):
        num = int((array[i] - min_value) * (bucket_num - 1) / d)
        bucket = bucket_list[num]
        bucket.append(array[i])
    # 4.对每个桶进行内部排序,此处调用python内部的sort函数,归并排序原理,注意归并排序并不稳定,最好用冒泡之类的进行稳定排序,保证桶排序的稳定性
    for i in range(len(bucket_list)):
        bucket_list[i].sort()
    # 5.输出全部元素
    sort_array = []
    for sub_list in bucket_list:
        for element in sub_list:
            sort_array.append(element)
    return sort_array


myarray1 = list([-1.2, 4.6, 3.5, 4.4, 5.5, 6.8, 7.9, 15.2, 1.1, 16.4])
myarray2 = bucket_sort(myarray1)
print(myarray2)



'''
[-1.2, 1.1, 3.5, 4.4, 4.6, 5.5, 6.8, 7.9, 15.2, 16.4]

'''

基数排序

  • 思路:整数按位数切割成不同的数字,然后按每个位数分别比较。基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。

  • MSD:先从高位开始进行排序,在每个关键字上,可采用计数排序(计数排序其实也是桶排序)

  • LSD:先从低位开始进行排序,在每个关键字上,可采用桶排序

  • 注意,内部排序的方式一定是稳定排序,不然结果会出错,所以用计数排序和桶排序

  • MSD的方式由高位数为基底开始进行分配,但在分配之后并不马上合并回一个数组中,而是在每个“桶子”中建立“子桶”,将每个桶子中的数值按照下一数位的值分配到“子桶”中。在进行完最低位数的分配后再合并回单一的数组中。

  • LSD:不需要子桶排序

def radix_sort(array=[]):
    """基数排序"""
    i = 0  # 记录当前正在排拿一位,最低位为1
    max_num = max(array)  # 最大值
    j = len(str(max_num))  # 记录最大值的位数
    bucket_list = []

    while i < j:
        bucket_list = [[] for _ in range(10)]  # 初始化桶数组,重新初始化10个列表集合
        # bucket_list.clear()
        # for num in range(0, 10):
        #     bucket_list.append([])  # 初始化桶列表
        for x in array:
            bucket_list[int(x / (10 ** i)) % 10].append(x)  # 找到位置放入桶数组
        array.clear()
        for x in bucket_list:  # 直接放回原序列
            for y in x:
                array.append(y)
        i += 1


myarray1 = list([334, 5, 67, 345, 7, 345345, 99, 4, 23, 78, 45, 1, 3453, 23424])
radix_sort(myarray1)
print(myarray1)
  • MSD:最高位优先法通常是一个递归的过程:

    <1>先根据最高位关键码K1排序,得到若干对象组,对象组中每个对象都有相同关键码K1。

    <2>再分别对每组中对象根据关键码K2进行排序,按K2值的不同,再分成若干个更小的子组,每个子组中的对象具有相同的K1和K2值。

    <3>依此重复,直到对关键码Kd完成排序为止。

    <4>最后,把所有子组中的对象依次连接起来,就得到一个有序的对象序列。

'''
    max_num = max(array)  # 最大值
    j = len(str(max_num))  # 记录最大值的位数
    d = j-1
'''

def radix_sort_msd(d, array=[]):
    length = len(array)
    bucket_list = [[] for _ in range(10)]  # 初始化桶数组,重新初始化10个列表集合
    if d >= 0 and length > 1:
        for x in array:
            bucket_list[int(x / (10 ** d)) % 10].append(x)  # 找到位置放入桶数组
    else:
        return
    # 进行子桶排序
    for i in range(10):
        radix_sort_msd(d - 1, bucket_list[i])
    array.clear()
    for x in bucket_list:  # 子桶更改原序列
        for y in x:
            array.append(y)


myarray1 = list([334, 5, 67, 345, 7,27, 345345, 99, 4, 23, 78, 45,46, 1, 3453, 23424])
radix_sort_msd(4, myarray1)
print(myarray1)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值