1 数据结构与算法初步 栈 队列 顺序表 链表

数据结构与算法初步

1 数据结构与算法

1.1 介绍

数据结构是以某种形式将数据组织在一起的集合,它不仅存储数据,而且支持访问和处理数据的操作。
算法是求解问题时所需要遵循的、被清楚指定的简单指令的集合,表示的是求解问题的一种实现思路或思想。优秀的算法可以让程序在短时间,消耗资源较少的条件下获得执行结果。
数据结构与算法思想具有广泛的通用性,在任何语言中都可以使用,仅仅是语法存在差异。

1.2 算法与时间复杂度
1.2.1 例子

计算 a a a b b b c c c
a + b + c = 1000 a 2 + b 2 = c 2 a + b + c = 1000 \\ a^2 + b^2 = c^2 a+b+c=1000a2+b2=c2

方法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)

两种方法计算结果相同,但方法1的运行耗时比方法2的运行耗时长很多,因此在运行耗时的角度上,方法2更加优秀。

1.2.2 评判程序优劣的方法
  1. 消耗计算机的资源和执行效率
    不推荐,因为数据不能从代码中直接获取,而且与计算机硬件条件有关。
  2. 计算程序运行耗时
    适当推荐,因为数据会受计算机硬件条件以及执行环境的影响。
  3. 时间复杂度
    推荐
1.2.3 列表操作耗时与timeit模块

操作:向列表中添加元素,获取不同方式所需耗时。
这里timeit模块获取一段代码的耗时。

class timeit.Timer(stmt='pass', setup='pass', timer=<timer function>)

参数说明
stmt:需要测试的代码;
setup:初始化代码或构建环境的导入语句;
timer:计时函数。
stmt参数和setup参数默认值都是'pass',可以包含多条语句,多条语句之间使用分号或新行分隔。

实例化一个空列表,然后将1000个数字添加到列表中。

def test1():
    alist = []
    for i in range(1000):
        alist.append(i)

def test2():
    alist = []
    for i in range(1000):
        alist = alist + [i]

def test3():
    alist = [i for i in range(1000)]

def test4():
    alist = list(range(1000))
from timeit import Timer

if __name__ == '__main__':
    t1 = Timer('test1()', 'from __main__ import test01')
    print(t1.timeit(1000))  # 0.04284289999986868
    
    t1 = Timer('test2()', 'from __main__ import test02')
    print(t1.timeit(1000))  # 0.8767927999999756
    
    t1 = Timer('test3()', 'from __main__ import test03')
    print(t1.timeit(1000))  # 0.022221100000024308
    
    t1 = Timer('test4()', 'from __main__ import test04')
    print(t1.timeit(1000))  # 0.009096400000089488
1.2.4 时间复杂度

算法的时间复杂度是一个函数,可以量化算法的执行步骤数量,用于定性地描述算法的运行时间,也可以了解输入值趋近无穷时算法的运行情况。
时间复杂度常用大O符号表示(大O记法)。
例如,上面例子中方法1的时间复杂度为 O ( n 3 ) O(n^3) O(n3),方法2的时间复杂度为 O ( n 2 ) O(n^2) O(n2)

常见的时间复杂度排序
O ( 1 ) < O ( l o g n ) < O ( n ) < O ( n l o g n ) < O ( n 2 ) < O ( n 3 ) < O ( 2 n ) < O ( n ! ) < O ( n n ) O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n) O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)

1.3 数据结构
1.3.1 介绍

数据结构是数据的某种组织方式,主要研究的是数据以何种形式保存或进行存取操作。
算法是为了解决实际问题而设计的,而数据结构是算法需要处理问题的载体。

1.3.2 例子
[{'name': 'name1', 'score': 'score1'},
 {'name': 'name2', 'score': 'score2'},
 {'name': 'name3', 'score': 'score3'}]

方式1中查询操作的时间复杂度为 O ( n ) O(n) O(n)

[('name1', 'score1'), ('name2', 'score2'), ('name3', 'score3')]

方式2中查询操作的时间复杂度为 O ( n ) O(n) O(n)

{'name1': {'score': 'score1'}, 'name2': {'score': 'score2'}, 'name3': {'score': 'score3'}}

方式3中查询操作的时间复杂度为 O ( 1 ) O(1) O(1)

2 栈与队列

2.1 介绍

栈Stack与队列Queue都是用于存储数据的数据结构。
栈:后入先出
队列:后入先出

2.2 栈

创建一个栈,需求:

Stack() 创建一个空栈,不需要参数。
push(item) 入栈(压栈)操作,将一个数据添加到栈的顶部。
pop() 出栈(弹栈)操作,将栈中顶部的元素取出。
peek() 返回栈顶元素的下标,下标起始值为0。
isEmpty() 判断栈是否为空。
size() 返回栈中元素的数量。
class Stack():
    def __init__(self):
        self.items = []

    def push(self, 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)

    def peek(self):
        return len(self.items) - 1
s = Stack()
s.push(1)
s.push(2)
s.push(3)
print(s.peek())  # 2
print(s.pop())  # 3
print(s.pop())  # 2
print(s.pop())  # 1
2.3 队列

创建一个队列,需求:

Queue() 创建一个空队列。
enqueue(item) 入队操作,将一个数据添加到队首。 
dequeue() 出队操作,从队尾取出元素。
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 size(self):
        return len(self.items)

    def isEmpty(self):
        return self.items == []

q = Queue()
q.enqueue(1)
q.enqueue(2)
q.enqueue(3)
print(q.dequeue())  # 1
print(q.dequeue())  # 2
print(q.dequeue())  # 3
2.4 约瑟夫问题

烫手的山芋
6个孩子围成一个圈,第一个孩子手里有一个烫手的山芋,需要在计时器开始计时1秒后将山芋传递给下一个孩子,以此类推。
计时器每计时7秒钟,手里有山芋的孩子退出游戏,直到只剩下一个孩子。请使用队列实现这个游戏策略,排在第几个位置的孩子会获胜。

# 将6个孩子加入到队列中
kids = ['A', 'B', 'C', 'D', 'E', 'F']
q = Queue()
for kid in kids:
    q.enqueue(kid)

while q.size() > 1:  # 结束游戏的条件
    # 开始一轮游戏
    for i in range(6):  # 山芋传递的次数=计时器间隔-1
    	# 保证队首的孩子手中有山芋
        kid = q.dequeue()
        q.enqueue(kid)
        
    # 一轮游戏结束时需要将队首元素移出队列(将手中有山芋的孩子淘汰)
    q.dequeue()
print(q.dequeue())  # E
2.5 两个队列实现栈
q1 = Queue()
q2 = Queue()
items = [1, 2, 3, 4, 5, 6]
for item in items:
    q1.enqueue(item)

while q1.size() >= 1:
    # 将q1中的前n-1个元素取出,依次加入到q2中保存起来
    while q1.size() > 1:
        item = q1.dequeue()
        q2.enqueue(item)

	# 剩下的元素是最后加入的元素,将其移出,即实现了栈的后入先出。
    print(q1.dequeue())
    # 交换队列
    q1, q2 = q2, q1
2.6 双端队列

同普通队列相比,双端队列可以在头和尾双端进行数据的插入和删除。

Deque() 创建一个deque。
add_front(item) 将数据添加到deque的头部。
add_rear(item) 将数据添加到deque的尾部。
remove_front() 从deque的头部取出元素。
remove_rear() 从deque的尾部取出元素。
is_empty() 判断deque是否为空。
size() 返回deque中元素的数量。
class Dequeue():
    def __init__(self):
        self.items = []

    def add_front(self, item):
        self.items.insert(0, item)

    def add_rear(self, item):
        self.items.append(item)

    def remove_front(self):
    	return self.items.pop(0)
        
    def remove_rear(self):
        return self.items.pop()

    def is_empty(self):
        return self.items == []

    def size(self):
        return len(self.items)

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

def is_huiwen(input_str):
    d = Dequeue()
    for each_char in input_str:
        d.add_front(each_char)
    flag = True
    while d.size()>1:
        if d.remove_front() != d.removeRear():
            flag = False
    return flag 

print(is_huiwen('abaa'))  # False

3 顺序表与链表

3.1 顺序表

将所有元素依次不间断地的存入一组连续的内存空间中,这种存储结构是顺序结构。采用顺序存储结构的线性表称为顺序表(Contiguous List),顺序表内元素是有顺序的。
顺序表分为单数据类型和多数据类型,Python中的列表和元组属于多数据类型的顺序表。

优点
存取操作简单高效,通过下标直接操作元素。
缺点
创建顺序表前需要预先确定待存储数据的数量和类型,以便在内存中申请连续的存储空间;
当需要插入或删除内部的一个元素时,整个顺序表需要遍历并移动元素来重新排序,相当于数据搬迁。

3.2 链表
3.2.1 介绍

链表(Linked List),是一种物理存储单元上非连续、非顺序的存储结构,元素的逻辑顺序是通过链表中的指针链接次序实现的。
链表中的元素没有下标,存储的每一个元素称为一个结点,每个结点包括两个部分:存储数据元素的数据域和存储下一个结点地址的指针域。

3.2.2 构建链表

节点数据结构的封装

class Node():
    def __init__(self, item):
        self.item = item
        self.next = None
is_empty():链表是否为空
length():链表长度
travel():遍历整个链表
add(item):链表头部添加元素
append(item):链表尾部添加元素
insert(pos, item):指定位置添加元素
remove(item):删除节点
search(item):查找节点是否存在
class LinkedList():
    def __init__(self):  # 构建一个空的链表
        self._head = None  # 只可以指向第一个节点

    def add(self, item):
        node = Node(item)
        node.next = self._head
        self._head = node

    def traval(self):
        cur = self._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 is_empty(self):
        return self._head == None

    def search_item(self, item):
        find = False
        cur = self._head
        while cur:
            cur_item = cur.item  # 遍历到节点对应的数据值
            if item == cur_item:
                find = True
                break
            cur = cur.next
        return find

    def append(self, item):  # 向链表尾部添加节点
        node = Node(item)

        if self._head == None:  # 链表为空
            self._head = node
            return
        # 链表为非空
        cur = self._head
        pre = None  # 永远指向cur的前一个节点
        while cur:
            pre = cur
            cur = cur.next
        # 循环结束后pre指向链表的最后一个节点,cur指向空
        pre.next = node

    def insert(self, pos, item):  # 向指定位置插入节点
        node = Node(item)
        pre = None
        cur = self._head
        if (pos < 0) or (pos > self.length()):
            print('位置有误,重新输入!!!')
            return
        if pos == 0:
            node.next = self._head
            self._head = node
            return
        for i in range(pos):
            pre = cur
            cur = cur.next
        pre.next = node
        node.next = cur

    def remove(self, item):  # 删除指定的节点
        cur = self._head
        pre = None
        # 如果删除节点为第一个节点的话
        if self._head.item == item:
            self._head = cur.next
            return
        while cur:
            pre = cur
            cur = cur.next
            if cur.item == item:
                break
        pre.next = cur.next
l = LinkedList()
l.append(1)
l.append(2)
l.append(3)
l.append(4)
l.append(5)
l.remove(5)
l.traval()
3.2.3 链表倒置
class LinkedList():
	def reverse(self):
        if self.is_empty():
            return None
        
        previous_node = None
        current_node = self._head
        next_node = current_node.next
        
        while True:
        	# 修改指针指向,即反转链表。
            current_node.next = previous_node
            # 进行偏移
            previous_node = current_node
            current_node = next_node
            if next_node:
                next_node = next_node.next
            else:
            	break
            	
        self._head = previous_node
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值