Python基础学习笔记 —— 数据结构与算法_数据结构与算法python笔记

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以戳这里获取

print(arr[::2]) # [1, 3, 5]

print(arr[::-1]) # [1, 5, 4, 3, 2, 1]

print(arr[:-1]) # [1, 2, 3, 4, 5]


### 1.2 链表


  

**1. 链表的基本结构**


链表主要包括单向链表和双向链表,这是一种无须在内存中顺序存储即可保持数据之间逻辑关系的数据结构。


链表是由一个个结点(Node)连接而成的,每个结点都是包含数据域(Data)和指针域(Next)的基本单元。其基本元素如下:


* 链表结点:每个结点分为两部分,即数据域和指针域
	+ 数据域:数据域内一般存储的是整型、浮点型等数字类型
	+ 指针域:指针域内一般存储的是下一个结点所在的内存空间地址
* 头结点:指向链表的第一个结点
* 尾结点:指向链表的最后一个结点
* None:链表的最后一个结点的指针域,为空


单链表


单链表的每个结点的指针域只指向下一个结点,整个链表是无环的


![在这里插入图片描述](https://img-blog.csdnimg.cn/f1150625bdfb4a38b256a81a5f050daf.png)


双向链表


![在这里插入图片描述](https://img-blog.csdnimg.cn/e79865be4b5a46b5af7dd6c0bbad2bd1.png)


单向循环链表


![在这里插入图片描述](https://img-blog.csdnimg.cn/3fac32af1c44423cbb3c027f1ead18c2.png)


相比于数组,在链表中执行插入、删除等操作可以使得操作效率大大提高。


以单链表为例,在数组内如想要删除或者插入元素到某一位置,该位置之后的所有元素都需要象前或者向后移动,这样一来,时间复杂度就与数组的长度有关,为O(n);但是在单链表中,仅仅需要通过改变所要删除或者插入位置前后结点的指针域即可,时间复杂度为O(1)。


**2. 单链表的实现与基本操作**



链表结点

class Node(object):
def __init__(self, item):
self.item = item
self.next = None

单链表

class SingleLink(object):
# 选择该初始化方法,调用时使用 SingleLink(node)
def __init__(self, node=None):
self.head = node

# 选择该初始化方法,调用时就不能使用上面的 SingleLink(node),初始化只能通过append添加结点
# def \_\_init\_\_(self):
# self.head = None

# 判断单链表是否为空
def is\_empty(self):
    if self.head is None:
        return True
    else:
        return False

# 获取链表长度
def length(self):
    cur = self.head
    count = 0
    while cur is not None:
        cur = cur.next
        count += 1
    return count

# 遍历链表
def travel(self):
    cur = self.head
    while cur is not None:
        print(cur.item, end=" ")
        cur = cur.next

# 链表头部增加结点
def add(self, item):
    node = Node(item)
    node.next = self.head
    self.head = node

# 链表尾部增加结点
"""

注意:如果链表为空链表,cur是没有next的,只需self.head=node
“”"

def append(self, item):
    node = Node(item)
    if self.is_empty():
        self.head = node
    else:
        cur = self.head
        while cur.next is not None:
            cur = cur.next
        cur.next = node

# 链表指定位置增加结点
"""

注意:这个只适用于链表中不存在重复元素的,要区别LeetCode203. 移除链表元素(这个题链表里会存在重复元素)
“”"
def insert(self, pos, item):
if pos == 0:
self.add(item)
elif pos >= self.length():
self.append(item)
else:
node = Node(item)
cur = self.head
count = 0
while count < pos - 1:
cur = cur.next
count += 1
node.next = cur.next
cur.next = node

# 删除结点
def remove(self, item):
    cur = self.head
    pre = None
    while cur is not None:
        # 找到了要删除的元素
        if cur.item == item:
            # 如果要删除的位置在头部
            if cur == self.head:
                self.head = cur.next
            # 要删除的位置不在头部
            else:
                pre.next = cur.next
            return  # 删除元素后及时退出循环
        # 没有找到要删除的元素
        else:
            pre = cur
            cur = cur.next

# 查找结点
def search(self, item):
    cur = self.head
    while cur is not None:
        if cur.item == item:
            return True
        cur = cur.next
    return False

### 1.3 队列


  

**1. 队列的基本结构**


队列最基本的特点就是先进先出,在队列尾部加入新元素,在队列头部删除元素,分为双端队列和一般的单端队列。



> 
> 队列的作用:对于任务处理类的系统,即先把用户发起的任务请求接收过来存到队列中,然后后端开启多个应用程序从队列中取任务进行处理,队列起到了 **缓冲压力** 的作用
> 
> 
> 


**2. 队列的实现与基本操作**


利用列表来简单地模拟队列



class Queue(object):
def __init__(self):
self.items = []

# 入队
def enqueue(self, item):
    self.items.append(item)

# 出队
def dequeue(self):
    self.items.pop(0)

# 队列的大小
def size(self):
    return len(self.items)

# 判断队列是否为空
def is\_empty(self):
    return self.items == []

对于队列这种数据结构,Python的 queue 类模块中提供了一种先进先出的队列模型 Queue,可以限制队列的长度也可以不限制,在创建队列时利用 `Queue(maxsize=0)`,maxsize小于等于0表示不限制,否则表示限制。


我们在编程的过程中也可以通过调用现有类来实现队列



from queue import Queue

队列的定义

q = Queue(maxsize=0)

put() 在队列尾部添加元素

q.put(1)
q.put(2)

print(q) # <queue.Queue object at 0x0000020095EE82B0>

print(q.queue) # deque([1, 2])

get() 在队列头部取出元素,返回队列头部元素

q.get()
print(q.queue) # deque([2])

empty() 判断队列是否为空

print(q.empty()) # False

full(0 判断队列是否达到最大长度限制

print(q.full()) # False

qsize() 队列当前的长度

print(q.qsize()) # 1


**3. 双端队列的实现与基本操作**


双端队列(deque,全名double-ended queue ), 是一种具有队列和栈的性质的数据结构  
 双端队列中的元素可以从两端弹出,其限定插入和删除操作在表的两端进行。双端队列可以在队列任意一端入队和出队。



class deque(object):
def __init__(self):
self.items = []

# 判断是否为空
def is\_empty(self):
    return self.items == []

# 队列的大小
def size(self):
    return len(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):
    self.items.pop(0)

# 尾部删除数据
def remove(self):
    self.items.pop()

### 1.4 栈


  

**1. 栈的基本结构**


栈最突出的特点是先进后出,其插入、删除操作均在栈顶进行。栈一般包括入栈、出栈操作,并且有一个顶指针(top)用于指示栈顶的位置


**2. 栈的实现与基本操作**



class Stack(object):
def __init__(self):
self.items = []

# 进栈
def push(self, item):
    self.items.append(item)

# 出栈
def pop(self):
    self.items.pop()

# 遍历
def travel(self):
    for i in self.items:
        print(i)

# 栈的大小
def size(self):
    return len(self.items)

# 栈是否为空
def is\_empty(self):
    return self.items == []
    # return len(self.items) == 0

# 返回栈顶元素
def peek(self):
	if self.is_empty():
		return "栈空"
    return self.items[self.size()-1]
    # return self.items[-1]

### 1.5 堆


  

Python 中 `heapq` 模块是 **小顶堆**


实现 **大顶堆** 方法: 小顶堆的插入和弹出操作均将元素 **取反** 即可



from heapq import *
from random import shuffle

data = list(range(10))
shuffle(data)
print(f’原始数据为:{data}‘)
small_heap = []
for num in data:
heappush(small_heap, num)
print(f’创建的小顶堆为:{small_heap}’)

heap = []
for num in data:
heappush(heap, -num)
print(heap)
big_heap = [-heappop(heap) for _ in range(len(heap))]
print(f’输出的大顶堆为:{big_heap}')

总体思路:负负得正


[参考](https://bbs.csdn.net/topics/618545628)


### 1.6 二叉树


  

**1. 树**


树是一种数据结构,它是由 n 个有限结点组成的一个具有层次关系的集合。


树的基本性质如下:


![在这里插入图片描述](https://img-blog.csdnimg.cn/f41e51f8c29c469bac4013452b8eda6c.png)


**2. 二叉树的基本结构**


二叉树则是每个结点最多有两个子树的树结构,通常子树被称作“左子树”和“右子树”。


二叉树的一般性质:


* 二叉树是有序的(左右子树不能颠倒)
* 二叉树的第 k 层上的结点数目最多为  
 
 
 
 
 
 2 
 
 
 
 k 
 
 
 − 
 
 
 1 
 
 
 
 
 
 2^{k-1} 
 
 
 2k−1
* 深度为 h 的二叉树最多有  
 
 
 
 
 
 2 
 
 
 h 
 
 
 
 − 
 
 
 1 
 
 
 
 2^h-1 
 
 
 2h−1 个结点
* 设非空二叉树中度为0、1 和 2 的结点个数分别为 
 
 
 
 
 
 n 
 
 
 0 
 
 
 
 
 n\_0 
 
 
 n0​ 、 
 
 
 
 
 
 n 
 
 
 1 
 
 
 
 
 n\_1 
 
 
 n1​ 和  
 
 
 
 
 
 n 
 
 
 2 
 
 
 
 
 n\_2 
 
 
 n2​,则  
 
 
 
 
 
 n 
 
 
 0 
 
 
 
 = 
 
 
 
 n 
 
 
 2 
 
 
 
 + 
 
 
 1 
 
 
 
 n\_0 = n\_2+1 
 
 
 n0​=n2​+1(叶子结点比二分支结点多一个)



> 
> **注意:这里的层数 k ,深度 h 均是从 1 开始的**
> 
> 
> 


其他常见的二叉树:


![在这里插入图片描述](https://img-blog.csdnimg.cn/222b1bea030443cbb8f578a08339a1fd.png)



> 
> 注意:
> 
> 
> * 如果按层序从0开始编号,结点 i 的左孩子为:2i+1,结点 i 的右孩子为:2i+2,结点 i 的父结点为:(i-1)//2
> * 如果结点按层序从0开始编号,假设共有 n 个结点,若 i <= n//2-1 ,该结点为非终端结点,若 i > n//2-1 ,该结点为终端结点
> 
> 
> 


![在这里插入图片描述](https://img-blog.csdnimg.cn/d417a57a3d354c13951a9adc9bbd176c.png)



> 
> 二叉树通常以链式存储
> 
> 
> 


**3. 二叉树的实现与基本操作**



定义结点类

class Node(object):
def __init__(self, item):
self.item = item
self.lchild = None
self.rchild = None

定义二叉树

class BinaryTree(object):
def __init__(self, node=None):
self.root = node

"""

思路分析:首先在队列中插入根结点,取出该结点,再判断该结点的左右子树是否为空,
左子结点不空,将其入队,右子结点不空,将其入队,
再分别判断左右结点的左右子结点是否为空,
循环往复,直到发现某个子结点为空,即把新结点添加进来
“”"

# 添加结点
def add(self, item):
    node = Node(item)
    # 二叉树为空
    if self.root is None:
        self.root = node
        return

    # 二叉树不空
    queue = []
    queue.append(self.root)
    # 编译环境会提示,也可以直接写成:queue = [self.root]

    while True:
        # 从队头取出数据
        node1 = queue.pop(0)
        # 判断左结点是否为空
        if node1.lchild is None:
            node1.lchild = node
            return
        else:
            queue.append(node1.lchild)
        # 判断右结点是否为空
        if node1.rchild is None:
            node1.rchild = node
            return
        else:
            queue.append(node1.rchild)

# 广度优先遍历,也叫层次遍历
def breadth(self):
    if self.root is None:
        return

    queue = []
    queue.append(self.root)
    while len(queue) > 0:
        # 取出数据
        node = queue.pop(0)
        print(node.item, end=" ")

        # 判断左右子结点是否为空
        if node.lchild is not None:
            queue.append(node.lchild)
        if node.rchild is not None:
            queue.append(node.rchild)

# 深度优先遍历
# 先序遍历(根左右)
def preorder\_travel(self, root):
    if root is not None:
        print(root.item, end=" ")
        self.preorder_travel(root.lchild)
        self.preorder_travel(root.rchild)

# 中序遍历(左根右)
def inorder\_travel(self, root):
    if root is not None:
        self.inorder_travel(root.lchild)
        print(root.item, end=" ")
        self.inorder_travel(root.rchild)

# 后序遍历(左右根)
def postorder\_travel(self, root):
    if root is not None:
        self.postorder_travel(root.lchild)
        self.postorder_travel(root.rchild)
        print(root.item, end=" ")

if name == “__main__”:
tree = BinaryTree()
tree.add(1)
tree.add(2)
tree.add(3)
tree.add(4)

# 添加结点的代码逻辑就是将添加第一个结点设置为根结点
print(tree.root)
print()

# 层序遍历
tree.breadth()  # 1 2 3 4
print()
# 前序遍历(根左右)
tree.preorder_travel(tree.root)  # 1 2 4 3
print()

# # 中序遍历(左根右)
tree.inorder_travel(tree.root)  # 4 2 1 3
print()

# # 后序遍历(左右根)
tree.postorder_travel(tree.root)  # 4 2 3 1


> 
> 注意:
> 
> 
> * 广度优先遍历基于队列
> * 深度优先遍历基于栈
> 
> 
> 


**试试 LeetCode 相关题目吧**


* [102. 二叉树的层序遍历](https://bbs.csdn.net/topics/618545628)
* [107. 二叉树的层序遍历 II](https://bbs.csdn.net/topics/618545628)
* [144.二叉树的前序遍历](https://bbs.csdn.net/topics/618545628)
* [94. 二叉树的中序遍历](https://bbs.csdn.net/topics/618545628)
* [145. 二叉树的后序遍历](https://bbs.csdn.net/topics/618545628)
* [589. N 叉树的前序遍历](https://bbs.csdn.net/topics/618545628)
* [590. N 叉树的后序遍历](https://bbs.csdn.net/topics/618545628)
* [429. N 叉树的层序遍历](https://bbs.csdn.net/topics/618545628)



> 
> 注意:二叉树遍历相关的题目在LeetCode环境中,省略了添加结点的代码逻辑,以及定义二叉树类中的初始化,要注意区分、根据具体题目应变。另外,其输入输出都是列表的形式,要注意
> 
> 
> 


**4. 由遍历结果反推二叉树结构**


![在这里插入图片描述](https://img-blog.csdnimg.cn/6ace3ef1371f4a7684f145b85f4ca91d.png)


## 2 排序算法


  

**算法的稳定性**:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变则称这种排序算法是稳定的,否则称为不稳定的


* 不稳定的排序算法:选择排序、快速排序、希尔排序、堆排序
* 稳定的排序算法:冒泡排序、插入排序、归并排序、基数排序


### 2.1 冒泡排序


  

对要进行排序的数据中相邻的数据进行两两比较,将较大的数据放在后面,依次对所有的数据进行操作,直至所有数据按要求完成排序


如果有n个数据进行排序,总共需要比较 n-1 次


每一次比较完毕,下一次的比较就会少一个数据参与


![在这里插入图片描述](https://img-blog.csdnimg.cn/1d7883ac5e544ee7b51a0228e65f194a.png)



def bubble_sort(lis):
n = len(lis)
# 控制比较的轮数
for j in range(n - 1):
count = 0
# 控制每一轮的比较次数
# -1是为了让数组不要越界
# -j是每一轮结束之后, 我们就会少比一个数字
for i in range(n - 1 - j):
if lis[i] > lis[i + 1]:
lis[i], lis[i + 1] = lis[i + 1], lis[i]
count += 1
# 算法优化
# 如果遍历一遍发现没有数字交换,退出循环,说明数列是有序的
if count == 0:
break

if name == “__main__”:
lis = [2, 7, 3, 6, 9, 4]
bubble_sort(lis)
print(lis)



> 
> 总结:
> 
> 
> * 冒泡排序是稳定的
> * 最坏时间复杂度为 
>  
>  
>  
>  
>  O 
>  
>  
>  ( 
>  
>  
>  
>  n 
>  
>  
>  2 
>  
>  
>  
>  ) 
>  
>  
>  
>  O(n^2) 
>  
>  
>  O(n2)
> * 最优时间复杂度为 
>  
>  
>  
>  
>  O 
>  
>  
>  ( 
>  
>  
>  n 
>  
>  
>  ) 
>  
>  
>  
>  O(n) 
>  
>  
>  O(n),遍历一遍发现没有任何元素发生了位置交换终止排序
> 
> 
> 


### 2.2 快速排序


  

快速排序算法中,每一次递归时以第一个数为基准数 ,找到数组中所有比基准数小的。再找到所有比基准数大的。小的全部放左边,大的全部放右边,确定基准数的正确位置。



def quick_sort(lis, left, right):
# 递归的结束条件:left > right
if left > right:
return

# 存储临时变量,left0始终为0,right0始终为len(lis)-1
left0 = left
right0 = right

# 基准值
base = lis[left0]

# left != right
while left != right:
    # 从右边开始找寻小于base的值
    while lis[right] >= base and left < right:
        right -= 1

    # 从左边开始找寻大于base的值
    while lis[left] <= base and left < right:
        left += 1

    # 交换两个数的值
    lis[left], lis[right] = lis[right], lis[left]

# left=right
# 基准数归位
lis[left0], lis[left] = lis[left], lis[left0]

# 递归操作
quick_sort(lis, left0, left - 1)
quick_sort(lis, left + 1, right0)  # quick\_sort(lis, left + 1, right0)

if name == ‘__main__’:
lis = [1, 2, 100, 50, 1000, 0, 10, 1]
quick_sort(lis, 0, len(lis) - 1)
print(lis)



> 
> 总结:
> 
> 
> * 快速排序算法不稳定
> * 最好的时间复杂度: 
>  
>  
>  
>  
>  O 
>  
>  
>  ( 
>  
>  
>  n 
>  
>  
>  l 
>  


![img](https://img-blog.csdnimg.cn/img_convert/f09c726184d8a3296b3a38e741e04699.png)
![img](https://img-blog.csdnimg.cn/img_convert/97a91111e1e19a061cfb315ac9f11a9c.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

le lis[left] <= base and left < right:
            left += 1

        # 交换两个数的值
        lis[left], lis[right] = lis[right], lis[left]

    # left=right
    # 基准数归位
    lis[left0], lis[left] = lis[left], lis[left0]

    # 递归操作
    quick_sort(lis, left0, left - 1)
    quick_sort(lis, left + 1, right0)  # quick\_sort(lis, left + 1, right0)


if __name__ == '\_\_main\_\_':
    lis = [1, 2, 100, 50, 1000, 0, 10, 1]
    quick_sort(lis, 0, len(lis) - 1)
    print(lis)

总结:

  • 快速排序算法不稳定
  • 最好的时间复杂度:

O

(

n

l

[外链图片转存中…(img-MvVvCLiY-1715682192139)]
[外链图片转存中…(img-qTy3lWfw-1715682192139)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值