# day11数据结构

day11数据结构

目录:

  • 数据结构
    • 队列
    • 双端队列
    • 链表
    • 二叉树
  • 算法:
    • 二分查找
    • 冒泡
    • 选择
    • 快速
    • 插入
    • 希尔
    • 排序二叉树

什么是算法分析?

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

1

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)

2

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)

---->

0 500 500
200 375 425
375 200 425
500 0 500

评判程序优劣的方法

  • 消耗计算机资源和执行效率(无法直观)
  • 计算算法执行的耗时
  • 时间复杂度(推荐)

时间复杂度

  • 评判规则:量化算法执行的操作/执行步骤的数量
  • 最重要的项:时间复杂度表达式中最有意义的项
  • 使用大O记法来表示时间复杂度:O(算法执行步骤数量表达式中最有意义的一项)

def sumOfN(n):
theSum = 0
for i in range(1,n+1):
theSum = theSum + i

return theSum

print(sumOfN(10))

---->55

  • 案例:计算下列算法的时间复杂度

  • 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
    
    ###########################
    3 + 3*n*n + 2*n + 1
    3n**2 +2n + 4
    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,score],
    [name1,score1],
    
]
O(n)
(
    [name,score],
    [name1,score1],

)
O(n)
{
    {'name':'bobo'},
    {'score':100},
}

{
    name:{score:100},
    name2:{score:99}
}

O(1)
  • 三种组织形式基于查询的时间复杂度?

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

list(for in range()) 速度最快

其次是 [] 推到式

然后是???

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

  • 栈顶,栈尾

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

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

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

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

  • peek() 从栈返回顶部项,但不会删除它。不需要参数。 不修改栈。

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

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

class Stack():
    def __init__(self):
        self.items = []
    def push(self,item):
        self.items.append(item)
    def drop(self):
        return self.items.pop()
    def isEmpty(self):
        return self.items == []
    def size(self):
        return len(self.items)
s = Stack()
s.push(1)
len(s.items)
print(s.drop())
print(s.isEmpty())
print(s.size())

s.push('第一')
s.push('第二')
s.push('第三')
s.items  # ['第一', '第二', '第三']
# print(s.drop()) # 后进先出对的  # '第三

--->

1
True
0
s = Stack()

def getRequest(url):
    s.push(url)
def showCurenrUrl():
    print('当前页面展示的url:'+s.pop())
def back():
    print('回退按钮点击后显示的url:',s.pop())

getRequest('www.1.com')
getRequest('www.2.com')
getRequest('www.3.com')

showCurenrUrl()
back()
back()


---->

当前页面展示的url:www.3.com
回退按钮点击后显示的url: www.2.com
回退按钮点击后显示的url: www.1.com

队列

  • 队列:先进先出

  • 应用场景:

    • 我们的计算机实验室有 30 台计算机与一台打印机联网。当学生想要打印时,他们的打印任务与正在等待的所有其他打印任务“一致”。第一个进入的任务是先完成。如果你是最后一个,你必须等待你前面的所有其他任务打印
  • Queue() 创建一个空的新队列。 它不需要参数,并返回一个空队列。

  • enqueue(item) 将新项添加到队尾。 它需要 item 作为参数,并不返回任何内容。

  • dequeue() 从队首移除项。它不需要参数并返回 item。 队列被修改。

  • isEmpty() 查看队列是否为空。它不需要参数,并返回布尔值。

  • size() 返回队列中的项数。它不需要参数,并返回一个整数。

class Queue():
    def __init__(self):
        self.items = []
    def push(self,index,item):
        self.items.insert(index,item)
    def drop(self):
        return self.items.pop()
    def isEmpty(self):
        return self.items == []
    def size(self):
        return len(self.items)
    def peek(self): #最后一个的位置 因为从零开始
        return len(self.items)-1
    
q = Queue()
q.push(0,'第一')
q.push(0,'第二')
q.push(0,'第三')

q.items # ['第三', '第二', '第一']
# q.drop() # '第一'

q.peek()

烫手的山芋游戏

  • 案例:烫手的山芋
    • 烫手山芋游戏介绍:6个孩子围城一个圈,排列顺序孩子们自己指定。第一个孩子手里有一个烫手的山芋,需要在计时器计时1秒后将山芋传递给下一个孩子,依次类推。规则是,在计时器每计时7秒时,手里有山芋的孩子退出游戏。该游戏直到剩下一个孩子时结束,最后剩下的孩子获胜。请使用队列实现该游戏策略,排在第几个位置最终会获胜。
  • 结论:
    • 七秒即时到了之后,山芋被传递了6次。
    • 保证手里有山芋的孩子永远站在队列的头部
class Queue():
    def __init__(self):
        self.items = []
    def push(self,index,item):
        self.items.insert(index,item)
    def drop(self):
        return self.items.pop()
    def isEmpty(self):
        return self.items == []
    def size(self):
        return len(self.items)
    def peek(self): #最后一个的位置 因为从零开始
        return len(self.items)-1
# 7 秒 ,去除第一个人中手上不传出的1秒    
q1 = Queue()
kids = ['A','B','C','D','E','F']
for kid in kids : 
    q1.push(0,kid)    # 把数据添加进去
q1.items
while len(q1.items)>1:  # 当数据只有1个时,为游戏的胜者
    for i in range(6):
        a = q1.drop()	# 先删,后添加到最初位置,就是换顺序
        q1.push(0,a)    # ,一个个往后走,然后保证山芋在第一位
    q1.drop()        # 删除第一位
q1.items    

双端队列

  • 同同列相比,有两个头部和尾部。可以在双端进行数据的插入和删除,提供了单数据结构中栈和队列的特性

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

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

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

  • removeFront() 从 deque 中删除首项。它不需要参数并返回 item。deque 被修改。

  • removeRear() 从 deque 中删除尾项。它不需要参数并返回 item。deque 被修改。

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

  • size() 返回 deque 中的项数。它不需要参数,并返回一个整数。

class Queue():
    def __init__(self):
        self.items = []
        # 队列方式加到栈首
    def addfront(self,index,item):
        self.items.insert(index,item)
        # 栈模式,从前面取,就是双端队列
    def addback(self,item):
        self.items.append(item)
        
    def dropfront(self):
        return self.items.pop()
    def dropback(self):
        return self.items.pop(0)  # 123  先进先出   
    
    def isEmpty(self):
        return self.items == []
    def size(self):
        return len(self.items)
    def peek(self): #最后一个的位置 因为从零开始
        return len(self.items)-1
    
q = Queue()

q.addfront(0,1)
q.addfront(0,2)
q.addfront(0,3)
q.items
q.dropfront()
q.dropfront()
q.dropfront()
# 首先进先出

q.addback(1)
q.addback(2)
q.addback(3)
q.dropfront()
q.dropfront()
q.dropfront()
q.items
# 栈  删除方式就是栈了   [1]  


q.addback(1)
q.addback(2)
q.addback(3)

q.items
q.dropback()
q.dropback()
q.dropback()
# 后进后厨
尾先进新出: 两边都是  

双端队列应用案例:回文检查

回文是一个字符串,读取首尾相同的字符,例如,radar toot madam。

抄的老师的:

ab = 'abcdaddcba'
    
def huiwen(st):
    q = Queue()
    ex = 'True'
    for s in st:
        q.addback(s)
    while len(q.items)>1:
        if q.dropback()!= q.dropfront():
            ex = 'False'
            break 
    return ex
    
print(huiwen(ab))      
False

内存

  • 计算机的作用

    • 用来存储和运算二进制的数据
  • 衡量计算机内存大小的单位:

    • bit(位):
    • 字节:8bit
    • kb:1024字节
    • mb:1024kb
  • 问题:计算机如何计算1+2?

    • 必须先在计算机的内存开辟内存空间
    • 才将对应的数值存储到该内存中
  • 变量的概念

    • a = 10 在计算机中表示的含义(内存图)
    • 内存空间开辟好了之后,每一块内存空间都会有两个默认的属性
      • 内存空间的大小:算机内存大小的单位
      • 内存空间的地址:十六进制的数值
        • 地址作用:用来定位(追踪)指定内存空间,就可以获取该内存空间中存储的数据
    • 变量本质表示的是内存空间的地址,通常变量想要表示的就是内存地址对应的内存空间
  • 理解a=10的内存图(引用,指向)

    • 引用:变量
    • 指向:如果某一个变量/引用存储了某一块内存空间的地址后,则表示该变量指向了该内存地址所对应的内存空间。
  • 不同数据占用内存空间的大小

顺序表

  • 容器/数据结构中存储的元素是有顺序的,顺序表的结构可以分为两种形式:单数据类型(numpy)和多数据类型(list)。

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

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

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

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

链表:

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

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

要求

. is_empty():链表是否为空

. length():链表长度

. travel():遍历整个链表

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

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

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

. remove(item):删除节点

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

建立节点

class Node():
    def __init__(self,item): #建立一个节点,新节点,后续的还不知道,所以没有next

​        self.item = item	# 添加的值
​        self.next = None   # 链子指向,因为是新值,还没来得及建链,故None
node  = Node(3)       # 实例化一个


class Link():
    def __init__(self):
        # 建立一个空链表
        self._head = None   # 第一个指向None ,还没有添加进来
        
	def add(self,item): # 链表头部添加元素
        node = Node(item)   # 实例化一个, 没有后续,next=None  item=item 
        node.next = self._head  
        # 之前的head 1 空, 2次添加是上一个node,因为head被重新赋值了
        # 变成了上一个的node(实例化的) 下面一行代码 ,然后一直迭代
        self._head = node  # 给head指向,实例化的那个Node 第一次    	        

1565874673548

  • 这两个函数都是用的一样的逻辑:
    • 都是不知道循环次数,所以用while循环,然后,一直next ,直到为空,然后退出
   def travel(self): # 遍历整个链表
        cur = self._head # 地址
        while cur: # 最后为空 
            print(cur.item) # 值
            cur = cur.next # 下一个
    def length(self):	# 链表长度
        cur = self._head # 地址
        count = 0  
        while cur: # 最后为空
            print(cur.item) # 值
            count += 1 
            cur = cur.next # 下一个          
#             print('长度为',count)
        return count
  • 不要忘了_head就是代表的 节点
 def isEmpty(self):
        return self._head == None

  • for 循环 然后返回 find为True说明找到了 ,找不到,和上面一样的思想,next往下走
    def search(self,item):     #查找节点是否存在 
        cur = self._head 
        find = 'False' 
        for i in range(self.length()):
            if cur.item == item :
                find = 'True'
            else:
                cur = cur.next 
        return find
  • 犯得两个错误,因为copy的,结果冒号是中文,还没发现
  • 下面的cur,pre循环,链子的拼接
  • 1565875155955
#加到最后
#最后一个指向为none ,主要是对前一个操作
#因为最后一个next = None,而前一个,指向的是最后一个,所以要链一下最后一个,一条链
    def append(self,item):
        node = Node(item)
        cur = self._head # 地址
        
        # 注意的项:
        # 第一次添加,没有前一项 # 的时候直接,没有最后一个,next 
        if self.isEmpty():
            self._head = node 
            return  # 不要忘了,return 退出
        pre = None 
        while cur: # 最后为空
             
            pre = cur # 前一个    
            cur = cur.next # 下一个          
             
        pre.next = node 
	# 插入  
	# 没想到不难,会了append之后,改一下链子,别的不大动,
	# 值的注意的点,范围插入时,
	def insert(self, pos, item):
        node = Node(item)
        cur = self._head # 地址
        if 0 > pos or pos > self.length()+1:
            print(pos,'超范围,没法插入,请重新插入')
            return 
        
        pre = None 
         # 最后为空
        for i in range(1,pos): # 第几个位置 前面添加后天添加建立两个连接
            pre = cur  # 前一个
            cur = cur.next  # 下一个   
            
        node.next = pre.next # node.next ✔  
        					 # 顺序不能错了,先把后面的链子给了中间的
        pre.next = node      # 然后给前面的链子,中间的
 #删除节点
    def remove(self,item):
        node = Node(item)
        cur = self._head # 地址
        if self.search(item)=='False': #用搜索,查,在里面然后删
            return '不在里面,不用删除'
        if self.length()==1 or self.length():  #1个或者多个的时候不走下面循环
            self._head == None
            return 
        pre = None 
         # 最后为空  
        for i in range(1,self.length()): # 第几个位置 前面添加后天添加建立两个连接
            pre = cur   # 前一个    
            cur = cur.next   # 下一个   
            
        pre.next = cur.next   #上一个的链连接下一个的下一个的链,所以下一个断开连接 
        
        print('长度',self.length())  

结果打印

link = Link()
link.add(1)
link.add(2)
link.add(3)
# link.append(1)
# link.append(2)
# link.append(3)
# link.insert(2,4)
# link.insert(2,4)
# link.insert(4,4)
# link.insert(44,4)# 超范围的不行
# link.travel()
# link.length()  
# link.isEmpty()
# link.search(-1)
link.remove(1)
print(link.remove(4))
# link.search(1)
3
2
1
3
2
1
3
2
1
3
2
1
不在里面,不用删除
posted @ 2019-08-15 22:42  learnacode  阅读(124)  评论(0编辑  收藏  举报