算法和数据结构

day01

课程的重要性

  • 应付面试
    • 数据结构和算法
  • 讲解的内容
    • 数据结构
      • 栈,队列,链表,二叉树
    • 算法:
      • 二分查找
      • 选择排序
      • 冒泡排序
      • 插入排序
      • 希尔排序
      • 快速排序
      • 二叉树排序

什么是计算机科学?

  • 首先明确的一点就是计算机科学不仅仅是对计算机的研究,虽然计算机在科学发展的过程中发挥了重大的作用,但是它只是一个工具,一个没有灵魂的工具而已。所谓的计算机科学实际上是对问题、解决问题以及解决问题的过程中产生产生的解决方案的研究。例如给定一个问题,计算机科学家的目标是开发一个算法来处理该问题,最终得到该问题的解、或者最优解。所以说计算机科学也可以被认为是对算法的研究。因此我们也可以感受到,所谓的算法就是对问题进行处理且求解的一种实现思路或者思想。

如何形象化的理解算法

  • 一个常胜将军在作战之前都会进行战略的制定,目的是为了能够在最短的时间切成本消耗最低的情况下获取最终的胜利。如果将编码作为战场,则程序员就是这场战役的指挥官,你如何可以将你的程序可以在最短且消耗资源最小的情况下获取最终的执行结果呢?算法就是我们的策略!

意义

  • 数据结构和算法思想的通用性异常的强大,在任何语言中都被使用,它们将会是我们编码生涯中伴随我们最长久利器(左膀右臂)。有一定经验的程序员最终拼的就是算法和数据结构。
  • 数据结构和算法思想也可以帮助我们拓展和历练编码的思维,可以让我们更好的融入到编程世界的角角落落。

什么是算法分析?

  • 刚接触编程的学生经常会将自己编写的程序和别人的程序做比对,或许在比对的过程中会发现双方编写的程序很相似但又各不相同。那么就会出现一个有趣的现象:两组程序都是用来解决同一个问题的,但是两组程序看起来又各不相同,那么哪一组程序更好呢?
  • a+b+c = 1000 a2 + b2 = c**2 (a,b,c均为自然数),求出a,b,c可能的组合?
for a in range(0,1001):
    for b in range(0,1001):
        for c in range(0,1001):
            if a+b+c == 1000 and a**2+b**2 == c**2:
                print(a,b,c)
for a in range(0,1001):
    for b in range(0,1001):
        c = 1000-a-b
        if a+b+c == 1000 and a**2+b**2 == c**2:
                print(a,b,c)

评判程序优劣方法

  • 消耗计算机资源和执行效率(无法直观)
  • 计算算法执行的耗时(不推荐,因为会受机器和执行环境的影响)
  • 时间复杂度(推荐)

时间复杂度:衡量算法性能的好坏优劣

  • 评判规则:量化算法执行的操作/执行步骤的数量
  • 最重要的项:时间复杂度表达式中最有意义的项
    • 使用大O记法来标准化的表示执行步骤的数量
    • O(量化步骤表达式中最有意义的项)
def sumOfN(n):
    theSum = 0  #1
    for i in range(1,n+1):
        theSum = theSum + i #n

    return theSum #1

print(sumOfN(10))

#n+2
#O(n)
  • 案例:计算下列算法的时间复杂度
a=5
b=6
c=10
for i in range(n):
   for j in range(n):
      x = i * i
      y = j * j
      z = i * j
for k in range(n):
   w = a*k + 45
   v = b*b
d = 33

#4+3n**2+2n
#O(n**2)
  • 常见的时间复杂度:
    • O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)

数据结构

  • 概念:对于数据(基本类型的数据(int,float,char))的组织方式就被称作为数据结构。数据结构解决的就是一组数据如何进行保存,保存形式是怎样的。

  • 案例: 需要存储一些学生的学生信息(name,score),那么这些数据应该如何组织呢?查询某一个具体学生的时间复杂度是什么呢?(三种组织方式)

[{
    'name':'xxx',
    'score':'xxx'
},{
    'name':'xxx',
    'score':'xxx'
},{
    'name':'xxx',
    'score':'xxx'
}]
[{'name': 'xxx', 'score': 'xxx'},
 {'name': 'xxx', 'score': 'xxx'},
 {'name': 'xxx', 'score': 'xxx'}]
[
    ('name','score'),
    ('name','score'),
    ('name','score')
]
[('name', 'score'), ('name', 'score'), ('name', 'score')]
{
    'zhangsan':{'score':'xxx'},
    'lisi':{'score':'xxx'}
}

{'zhangsan': {'score': 'xxx'}, 'lisi': {'score': 'xxx'}}
  • 三种组织形式基于查询的时间复杂度?

  • 使用不同的形式组织数据,在基于查询时的时间复杂度是不一样的。因此认为算法是为了解决实际问题而设计的,数据结构是算法需要处理问题的载体。

目标

  • 本节的目标是告诉大家Python列表和字典操作的 大O 性能。然后我们将做一些基于时间的实验来说明每个数据结构的花销和使用这些数据结构的好处

实操

  • 在列表的操作有一个非常常见的编程任务就是是增加一个列表。我们马上想到的有两种方法可以创建更长的列表,可以使用 append 方法或拼接运算符。但是这两种方法那种效率更高呢。这对你来说很重要,因为它可以帮助你通过选择合适的工具来提高你自己的程序的效率。

  • 实例化一个空列表,然后将0-n范围的数据添加到列表中。(四种方式)

def test01():
    alist = []
    for i in range(0,100):
        alist += [i]
    return alist

def test02():
    alist = []
    for i in range(0,100):
        alist.append(i)
    return alist

if __name__ == '__main__':
    pass
  • timeit模块:该模块可以用来测试一段python代码的执行速度/时长。

  • Timer类:该类是timeit模块中专门用于测量python代码的执行速度/时长的。原型为:class timeit.Timer(stmt='pass',setup='pass')。

    • stmt参数:表示即将进行测试的代码块语句。

    • setup:运行代码块语句时所需要的设置。

    • timeit函数:timeit.Timer.timeit(number=100000),该函数返回代码块语句执行number次的平均耗时。

from timeit import Timer
def test01():
    alist = []
    for i in range(0,100):
        alist += [i]
    return alist

def test02():
    alist = []
    for i in range(0,100):
        alist.append(i)
    return alist

if __name__ == '__main__':
    timer = Timer('test01()','from __main__ import test01')
    print(timer.timeit(1000))
    
    t = Timer('test02()','from __main__ import test02')
    print(t.timeit(1000))
0.02650774001085665
0.02491867200296838
  • 四种方式中哪种方式添加列表元素的效率最高呢?
    • 计算运行平均耗时

  • 特性:先进后出的数据结构

  • 栈顶,栈尾

  • 应用:每个 web 浏览器都有一个返回按钮。当你浏览网页时,这些网页被放置在一个栈中(实际是网页的网址)。你现在查看的网页在顶部,你第一个查看的网页在底部。如果按‘返回’按钮,将按相反的顺序浏览刚才的页面。

  • Stack() 创建一个空的新栈。 它不需要参数,并返回一个空栈。

  • push(item)将一个新项添加到栈的顶部。它需要 item 做参数并不返回任何内容。

  • pop() 从栈中删除顶部项。它不需要参数并返回 item 。栈被修改。

  • isEmpty() 测试栈是否为空。不需要参数,并返回布尔值。

  • size() 返回栈中的 item 数量。不需要参数,并返回一个整数。

class Stack():
    def __init__(self): # 实例化一个空栈
        self.items = [] # 容器
    def push(self,item): # item就是向栈中添加的元素(从栈顶添加到栈底)
        self.items.append(item)
    def pop(self): # 弹出栈顶的元素
        return self.items.pop()
    def isEmpty(self): # 是否为空
        return self.items == []
    def size(self): # 栈的大小
        return len(self.items)
stack = Stack() # 实例化一个空栈
stack.push(1)
stack.push(2)
stack.push(3)

print(stack.pop())
print(stack.pop())
print(stack.pop())
"""
3
2
1
"""

队列

  • 队列:先进先出
  • 应用场景:
    • 我们的计算机实验室有 30 台计算机与一台打印机联网。当学生想要打印时,他们的打印任务与正在等待的所有其他打印任务“一致”。第一个进入的任务是先完成。如果你是最后一个,你必须等待你前面的所有其他任务打印
  • Queue() 创建一个空的新队列。 它不需要参数,并返回一个空队列。
  • enqueue(item) 将新项添加到队尾。 它需要 item 作为参数,并不返回任何内容。
  • dequeue() 从队首移除项。它不需要参数并返回 item。 队列被修改。
  • isEmpty() 查看队列是否为空。它不需要参数,并返回布尔值。
  • size() 返回队列中的项数。它不需要参数,并返回一个整数。
class Queue():
    def __init__(self):
        self.items = []
    def enqueue(self,item):
        self.items.insert(0,item)
    def dequeue(self):
        return self.items.pop()
    def isEmpty(self):
        return self.items == []
    def size(self):
        return len(self.items)
    
q = Queue()
q.enqueue(1)
q.enqueue(2)
q.enqueue(3)

print(q.dequeue())
print(q.dequeue())
print(q.dequeue())

"""
1
2
3
"""

案例:烫手的山芋

  • 烫手山芋游戏介绍:6个孩子围城一个圈,排列顺序孩子们自己指定。第一个孩子手里有一个烫手的山芋,需要在计时器计时1秒后将山芋传递给下一个孩子,依次类推。规则是,在计时器每计时7秒时,手里有山芋的孩子退出游戏。该游戏直到剩下一个孩子时结束,最后剩下的孩子获胜。请使用队列实现该游戏策略,排在第几个位置最终会获胜。
  • 关键因素:
    • 必须要保证手里有山芋的孩子永远在队头的位置
      • 结论:
        • 计时器计时7秒或者山芋被传递6次则一轮游戏结束,淘汰一个孩子,游戏继续。
"""
思路:
七秒传递六次,
每一次传递时都将得到山芋的孩子移到队列首(也就是将上一个孩子从队列中移除放在队列末),
判断只剩一个队列元素时,循环结束
"""

kids = ['A', 'B', 'C', 'D', 'E', 'F'] # 人员
kids_queue = Queue() # 创建队列
for kid in kids:
    kids_queue.enqueue(kid) # 加入队列

while kids_queue.size() > 1:
    for i in range(6): # 传递次数
        first_kid = kids_queue.dequeue() # 移除
        kids_queue.enqueue(first_kid) # 加入
    kids_queue.dequeue() # 淘汰

print(kids_queue.dequeue())

# E

内存

  • 计算机的作用

    • 存储和运算二进制的数据。
  • 问题:计算机如何计算1+2?

    • 首先需要将1和2这两个数值加载存储到计算机的内存中(必须保证计算机有足够的内存对数据进行存储)
    • 基于加法寄存器对相关的内存数据进行加法运算
  • 变量的概念

    • 计算机中的一块内存空间就是变量
    • 内存空间有两个默认的属性
      • 大小

        • 决定这块内存空间可以存储多大的数值
        • 衡量内存空间大小的单位
          • bit(位):1个bit值可以存放一位二进制的数据
          • byte(字节):8bit
          • kb:1024byte
          • mb:1024kb
          • gb:1024mb
          • t:1024g
      • 地址

        • 指定内存空间的定位,(16进制的数来表示)
  • 理解a=10的内存图(引用,指向)

    • 引用:变量。a=10,a就是变量或者引用。a实际存储的是10对应内存空间的地址
      image.png

    • 指向:如果某一个引用存储的是一块内存空间的地址,则该引用就指向了该块内存空间

      • a=10,a指向了10,a存储的是10所在的内存空间的地址
  • 不同数据占用内存空间的大小

    • a = 10,a=3.3,a='bobo'
    • 整数:4byte
    • 浮点数:8byte
    • 字符串:一个字符占用一个字节

顺序表

  • 集合中存储的元素是有顺序的,顺序表的结构可以分为两种形式:单数据类型(数组)和多数据类型(列表)。

  • python中的列表和元组就属于多数据类型的顺序表

  • 单数据类型顺序表的内存图(内存连续开启)
    image.png

  • 多数据类型顺序表的内存图(内存非连续开辟)

  • 顺序表的弊端:顺序表的结构需要预先知道数据大小来申请连续的存储空间,而在进行扩充时又需要进行数据的搬迁。

链表:

相对于顺序表,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理且进行扩充时不需要进行数据搬迁。

  • 链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是不像顺序表一样连续存储数据,而是每一个结点(数据存储单元)里存放下一个结点的信息(即地址)

image.png

#节点的数据结构封装
class Node():
    def __init__(self,item):
        self.item = item
        self.next = None #指向下一个节点的地址
node = Node('A') #实例化了一个节点对象,该节点存储的数据为A,next为None

# 封装链表
class Link():
    def __init__(self): # 构建空链表
        self._head = None # 空离链表_head指向None
    
    def add(self, item):# 在链表头部插入一个节点
        node = Node(item)
        node.next = self._head # 将新插入的节点的next指向之前的头节点
        self._head = node # _head永远指向头节点
        
    def is_Empty(self):
        return self._head == None
    
    def travel(self): # 遍历链表 循环查找并打印item值, 直到next为None
        cur = self._head # 新赋值一个临时变量保存_head的值, 防止改变_head的指向
        while cur:
            print(cur.item)
            cur = cur.next
    
    def length(self): # 统计节点的数量
        count = 0
        cur = self._head
        while cur:
            count += 1
            cur = cur.next
        return count
    
    def search(self, item): # 查看链表中是否有此节点,返回布尔值
        cur = self._head
        find = False
        while cur:
            if cur.item == item:
                find = True
                break
            cur = cur.next
        return find
    
    def append(self, item): # 向链表尾部添加一个节点
        node = Node(item)
        cur = self._head
        pre = cur # 创建临时变量用来保存cur前一个节点的信息
        while cur: # 在停止循环时,cur已经指向了None
            pre = cur
            cur = cur.next
        pre.next = node # 将最后一个节点的next指向新的节点
    
    def insert(self,pos,item):#pos用整数值
        node = Node(item)
        if self._head == None:#链表为空
            #将插入的节点作为链表的第一个节点即可
            self._head = node
            return 
        
        
        #如果插入的位置大于链表的长度或者小于0
        if pos > self.length():
            self.append(item)
            return
        if pos < 0:
            self.add(item)
            return
        #分析:必须找到插入位置对应的节点和其前一个节点就可以实现插入了
        pre = None #cur前一个节点
        cur = self._head #当前节点
        
        for i in range(pos): #循环pos次(控制pre和cur向后偏移)
            pre = cur
            cur = cur.next
            
        pre.next = node
        node.next = cur
            
                
    def remove(self,item):
        if self._head == None:
            return #链表为空则直接结束程序
        
        #需要定位到删除的节点和其前一个节点
        cur = self._head
        pre = None
        
        #如果删除的是第一个节点则需要单独做处理
        if cur.item == item:
            self._head = cur.next #让head指向第二个节点
            return
        #删除除了第一个节点其他的节点操作
        while True:
            pre = cur
            cur = cur.next
            if cur == None:#没有找到删除的节点.cur==None遍历到最后一个节点
                return
            if cur.item == item:#找到了删除的节点
                break 
            
        #定位到了删除的节点cur和其前一个节点pre
        pre.next = cur.next
            
        
            
link = Link()#构建了一个新的空链表
link.add('D')
link.add('C')
link.add('B')
link.add('A')
link.append('E')
link.insert(0, 'F')
link.insert(5, 'G')
link.remove('F')
link.travel()
A
B
C
D
E

. is_empty():链表是否为空

. length():链表长度

. travel():遍历整个链表

. add(item):链表头部添加元素

. append(item):链表尾部添加元素

. insert(pos, item):指定位置添加元素

. remove(item):删除节点

. search(item):查找节点是否存在

day02

二叉树:一个根节点只可以有两个子节点(两个分叉)

  • 节点:
    • 视为链表中的节点
  • 根节点
    • 就是树种最上面的第一个节点
  • 左叶子节点
  • 右叶子节点
  • 子树
    • 完整子树
      • 一个跟节点和两个完整的子节点
    • 非完整子树
  • 二叉树的遍历
    • 深度遍历:纵向遍历。二叉树中任意的一个节点都可以作为子树的根节点。
      • 跟,左,右特值的是子树中的相关位置的节点
        • 前序:跟左右:二叉树中所有子树按照指定顺序进行节点的遍历
        • 中序:左跟右
        • 后序:左右跟
    • 广度遍历:
      • 横向遍历
class Node():
    def __init__(self,item):
        self.item = item
        #将left和right当成链表中的next
        self.left = None #指向该节点的左叶子节点
        self.right = None#指向该节点的右叶子节点
        
class Tree():
    def __init__(self):#构造一个空树
        self.root = None#root就可以视为链表中的head,root永远要指向二叉树的根节点
    def addNode(self,item):
        #实例化一个新的节点对象
        node = Node(item)
        
        #如果树为空,则插入的节点为根节点
        if self.root == None:#树为空
            self.root = node
            return
        #树为非空
        cur = self.root
        node_list = [cur] #将根节点加入到列表中,该列表中存放的为左右叶子节点部位空的节点
        
        while True:
            p_node = node_list.pop(0)
            #非空的节点需要再次加入到node_list列表中
            if p_node.left != None:
                node_list.append(p_node.left)
            else:
                p_node.left = node
                break
            if p_node.right != None:
                node_list.append(p_node.right)
            else:#当前节点的右叶子节点为空
                p_node.right = node
                break
                
    def travel(self):
        if self.root == None:#树为空
            print('空树')
            return
        cur = self.root
        node_list = [cur]
        print(cur.item)
        while node_list:
            p_node = node_list.pop(0)

            if p_node.left != None:
                print(p_node.left.item)
                node_list.append(p_node.left)

            if p_node.right != None:
                print(p_node.right.item)
                node_list.append(p_node.right)
    #深度遍历
    def forward(self,root):#root表示的是某一个子树的根节点
        #结束递归的条件
        if root == None:
            return
        #跟,左,右
        print(root.item)
        #打印子树的左节点(另一个子树的根节点)
        self.forward(root.left) 
        #右
        self.forward(root.right)
        
    def middle(self,root):
        if root == None:
            return
        self.middle(root.left)
        print(root.item)
        self.middle(root.right)
        
    def back(self,root):
        if root == None:
                return
        self.back(root.left)
        self.back(root.right)
        print(root.item)
        
tree = Tree() #实例化了一颗空树。tree.root == None
tree.addNode(1)
tree.addNode(2)
tree.addNode(3)
tree.addNode(4)
tree.addNode(5)
tree.addNode(6)

tree.back(tree.root)
  • 递归的案例:
    • 斐波那契序列
    • n的阶乘

排序二叉树

  • 深度遍历主要是作用在排序二叉树种
    • 中序遍历作用在排序二叉中中,则遍历出来的结果就是一个有序的数列
class Node():
    def __init__(self,item):
        self.item = item
        #将left和right当成链表中的next
        self.left = None #指向该节点的左叶子节点
        self.right = None#指向该节点的右叶子节点
        
class SortTree():
    def __init__(self):
        self.root = None
    def insert(self,item):
        node = Node(item)
        if self.root == None:#树为空则插入的第一个节点为跟节点
            self.root = node
            return
        #树为非空
        cur = self.root
        #将插入的节点和根节点进行大小比较
        while True:
            if item >= cur.item:#插入的节点大于根节点,该节点应该插入到根节点的右侧
                #再次进行判断:如果根节点的右叶子节点为空,则将item插入到右叶子节点的位置
                if cur.right == None:
                    cur.right = node
                    return
                else:#根节点的右叶子节点不为空
                    cur = cur.right
            else:#插入的节点小于根节点,该节点插入到根节点的左侧
                if cur.left == None:
                    cur.left = node
                    return
                else:
                    cur = cur.left
    def middle(self,root):
        if root == None:
            return
        self.middle(root.left)
        print(root.item)
        self.middle(root.right)
        
        
tree = SortTree()
alist = [3,8,5,7,6,2,1,0]
for i in alist:
    tree.insert(i)
tree.middle(tree.root) #只有中序遍历在排序二叉树中遍历的结果为有序结果

day03

二分查找

def find(alist,item):
    #left&right表示序列的起始位置的下标
    left = 0
    right = len(alist)-1
    
    isFind = False#是否找到的标识
    
    while left <= right:
        mid_index = (right+left)//2 #序列中间元素的下标(序列中元素的个数整除2)

        #需要使用中间元素和要找寻的item进行比较
        if alist[mid_index] > item: #中间元素的值大于了要找寻的item的值。结论:item一定是存在于中间元素的左侧
            #将中间元素左侧作为一个新序列
            right = mid_index - 1
        else:#中间元素小于等于找寻的item
            if alist[mid_index] == item:#找到了
                isFind = True
                break
            else:#找的值大于中间元素,找寻的值在中间元素的右侧
                left = mid_index + 1 #将中间元素右侧作为了新的子序列
    
    return isFind

alist = [1,2,3,4,5,6]
print(find(alist,41))

冒泡排序

#将乱序序列中的最大值找出逐步偏移(让两两元素进行大小比较,大的元素逐步向后移动)到最后
def sort(alist):
    length = len(alist)
    #让两两元素进行比较(n-1)
    for i in range(0,length-1):
        if alist[i] > alist[i+1]:#第一个元素大于第二个元素
            #两个元素交换位置
            alist[i],alist[i+1] = alist[i+1],alist[i]
    return alist
        
alist = [3,8,5,2,0,7,6]
print(sort(alist))
[3, 5, 2, 0, 7, 6, 8]
#冒泡排序完整代码
#上述代码操作需要作用n-1才可以将整个序列变成有序的
def sort(alist):
    length = len(alist)
    
    for j in range(0,length-1): 
        #让两两元素进行比较(n-1)
        for i in range(0,length-1-j):
            if alist[i] > alist[i+1]:#第一个元素大于第二个元素
                #两个元素交换位置
                alist[i],alist[i+1] = alist[i+1],alist[i]
    return alist

alist = [3,8,5,2,0,7,6]
print(sort(alist))
[0, 2, 3, 5, 6, 7, 8]

选择排序

#将乱序序列中的最大值直接找出,然后将最大值和序列最后一个元素交换位置,最终我们就将最大值找出放置到了最后的位置
def sort(alist):
    max_index = 0 #表示的是最大值的下标,一开始我们假设列表中的第0个元素为最大值
    for i in range(len(alist)-1):#控制比较的次数
        if alist[max_index] < alist[i+1]:
            max_index = i+1 
    #将最大值和最后一个元素交换位置,就可以将最大值放置到序列的最后位置
    alist[max_index],alist[len(alist)-1] = alist[len(alist)-1],alist[max_index]
    
    return alist
    
    
alist = [3,8,5,2,0,7,6]
print(sort(alist))   
[3, 6, 5, 2, 0, 7, 8]
#完整代码
def sort(alist):
    for j in range(0,len(alist)-1):
        max_index = 0 #表示的是最大值的下标,一开始我们假设列表中的第0个元素为最大值
        for i in range(len(alist)-1-j):#控制比较的次数
            if alist[max_index] < alist[i+1]:
                max_index = i+1 
        #将最大值和最后一个元素交换位置,就可以将最大值放置到序列的最后位置
        alist[max_index],alist[len(alist)-1-j] = alist[len(alist)-1-j],alist[max_index]

    return alist
    
    
alist = [3,8,5,2,0,7,6]
print(sort(alist))
[0, 2, 3, 5, 6, 7, 8]

插入排序

  • 需要将原始的序列假装拆分成两部分

    • 有序部分:默认为序列中的第一个元素
    • 无序部分:默认为序列中除了第一个元素剩下的元素
    • 关键:将无序部分的元素逐一插入到有序部分中即可
  • 定义一个变量叫做i

    • i表示的是有序部分元素的个数
    • i还可以表示无序部分中第一个元素的下标
  • 原始序列:[3,8,5,2,6,10,1]

  • [3, 8,5,2,6,10,1] ==》i=1

  • [3,8, 5,2,6,10,1] ==》i=2

#[3,   8,5,2,6,10,1]  ==》i=1
i = 1 #i表示的是有序部分元素的个数
#alist[i]:无序部分的第一个元素
#alist[i-1]:有序部分的最后一个元素
if alist[i] < alist[i-1]: #有序部分的最后一个元素大于无序部分的第一个元素
    alist[i],alist[i-1] = alist[i-1],alist[i]
else:
    pass
#[6,8,   7,2,6,10,1]  ==》i=2
i = 2 #i表示的是有序部分元素的个数
while i > 0:
    if alist[i] < alist[i-1]:
        alist[i],alist[i-1] = alist[i-1],alist[i]#[6,5,8,    2,6,10,1]
        i -= 1 #i是不可以为负数
    else:
        break
#i的取值范围是1-(n-1)
for i in range(1,len(alist)):
#i = 2 #有序部分有两个元素
    while i > 0:
        if alist[i] < alist[i-1]:
            alist[i],alist[i-1] = alist[i-1],alist[i]#[6,5,8,    2,6,10,1]
            i -= 1 #i是不可以为负数
        else:
            break
#完整代码
def sort(alist):
    #i的取值范围是1-(n-1)
    for i in range(1,len(alist)):
    #i = 2 #有序部分有两个元素
        while i > 0:
            if alist[i] < alist[i-1]:
                alist[i],alist[i-1] = alist[i-1],alist[i]#[6,5,8,    2,6,10,1]
                i -= 1 #i是不可以为负数
            else:
                break
    return alist
alist = [3,8,5,2,0,7,6]
print(sort(alist))
[0, 2, 3, 5, 6, 7, 8]

希尔排序(扩展)

快速排序

  • alist = [66,13,51,76,81,26,57,69,23]

  • 基数:

    • 默认情况下序列中第一个元素作为基数
    • 原始序列中比基数大的值放置到基数右侧,比基数小的值放置到基数左侧
  • 将列表中第一个元素设定为基准数字,赋值给mid变量,然后将整个列表中比基准小的数值放在基准的左侧,比基准到的数字放在基准右侧。然后将基准数字左右两侧的序列在根据此方法进行排放。

  • 定义两个指针,low指向最左侧,high指向最右侧

  • 然后对最右侧指针进行向左移动,移动法则是,如果指针指向的数值比基准小,则将指针指向的数字移动到基准数字原始的位置,否则继续移动指针。

  • 如果最右侧指针指向的数值移动到基准位置时,开始移动最左侧指针,将其向右移动,如果该指针指向的数值大于基准则将该数值移动到最右侧指针指向的位置,然后停止移动。

  • 如果左右侧指针重复则,将基准放入左右指针重复的位置,则基准左侧为比其小的数值,右侧为比其大的数值。

#原始序列中比基数大的值放置到基数右侧,比基数小的值放置到基数左侧
def sort(alist):
    #low&hight表示的序列的起始位置的下标
    low = 0
    high = len(alist)-1 
    
    mid = alist[low] #基数
    
    while low < high:
        #将原始序列中比基数小的值放置在基数的左侧,比基数大的值放置在基数的右侧
        #注意:一开始的时候先将high向左偏移
        while low < high:
            if alist[high] < mid: #high小于基数
                alist[low] = alist[high]
                break
            else:#基数如果小于了high,将high向左偏移
                high -= 1 #high想左偏移了以为
        #low向右偏移
        while low <high:
            if alist[low] < mid:#如果low小于mid则让low想右偏移
                low += 1#让low向右偏移一位
            else:
                alist[high] = alist[low]
                break
    if low == high:#low和high重复,将基数赋值到low或者high的位置
        alist[low] = mid #alist[high] = mid
        
    return alist
        
        
        
    
        
#加入递归后完整的代码
def sort(alist,left,right):
    #low&hight表示的序列的起始位置的下标
    low = left
    high = right 
    
    #结束递归的条件
    if low > high:
        return
    
    mid = alist[low] #基数
    
    while low < high:
        #将原始序列中比基数小的值放置在基数的左侧,比基数大的值放置在基数的右侧
        #注意:一开始的时候先将high向左偏移
        while low < high:
            if alist[high] < mid: #high小于基数
                alist[low] = alist[high]
                break
            else:#基数如果小于了high,将high向左偏移
                high -= 1 #high想左偏移了以为
        #low向右偏移
        while low <high:
            if alist[low] < mid:#如果low小于mid则让low想右偏移
                low += 1#让low向右偏移一位
            else:
                alist[high] = alist[low]
                break
    if low == high:#low和high重复,将基数赋值到low或者high的位置
        alist[low] = mid #alist[high] = mid
    
    #指定操作作用到基数左侧的子序列中
    sort(alist,left,low-1)
    #指定操作作用到基数右侧的子序列中
    sort(alist,high+1,right)
    return alist
alist = [66,13,51,76,81,26,57,69,23]
print(sort(alist,0,len(alist)-1))
posted @ 2020-04-21 14:53  我的儿  阅读(158)  评论(0)    收藏  举报