最近开始系统的复习一遍数据结构,发现一本诙谐幽默的好书《大话数据结构》。
虽然它是用c实现的,而我习惯用python,但是理解原理还是相当不错的一本书,这里是看书1-3章的读书笔记。
分别内容是介绍数据结构的定义,简介算法,介绍了线性表,我还加入了python的程序实现单链表。
程序设计 = 数据结构+算法
数据 |
|||||||
数据对象 |
数据对象 |
||||||
数据元素 |
数据元素 |
数据元素 |
数据元素 |
||||
数据项1 |
数据项2 |
数据项1 |
数据项2 |
数据项1 |
数据项2 |
数据项1 |
数据项2 |
数据:描述客观事物的符号,能在计算机中操作的对象,能被计算机识别,并输入给计算机处理的符号集合。
数据元素:组成数据的,有一定意义的基本单位。
数据项:一个数据元素可以由若干个数据项组成。数据项是数据不可分割的最小单位。
数据对象:数据的子集,性质相同的数据元素的集合。
数据结构:存在一种或多种特定关系的数据元素的集合。分为逻辑结构和物理结构。
逻辑结构:数据对象中数据元素之间的相互关系。(集合、线性、树、图)
物理结构:数据的逻辑结构在计算机中的存储形式。存储结构形式有两种,顺序存储和链式存储。
顺序存储:数据存储在地址连续的基本存储单元中,逻辑结构和物理结构一致。
链式存储:把数据存放在任意的存储单元中,这组存储单元可以是连续的也可以是不连续的。
抽象数据类型
数据类型:一组性质相同的值的集合以及定义在此集合上的一些操作的总称。
抽象:从事物中提取出事物具有的普遍性本质。
算法:解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作。
算法五个基本特性:输入、输出、有穷(没有无限循环,在可接受时间内运行完毕)、确定性(语句没有二义性)和可行性。
算法设计要求:正确、可读、健壮(输入不合法时也有适合的应对,而不是乱输出数据)、时间复杂度,空间复杂度小。
算法效率度量方法:事后统计方法(不科学),事前分析估算。
算法时间复杂度:
T(n)=O(f(n)) ----大o阶表示法
n是问题规模,f(n)表示问题规模n的某个函数,T(n)是程序中语句执行的总次数,整体表示的含义是随着问题规模n的增大,算法执行时间增长率和f(n)的增长率相同,称作渐进时间复杂度。随着n的增加,T(n)增长最慢的算法为最优算法。
如何推算大o阶:计算执行次数的最高阶项。
循环的时间复杂度等于循环体的复杂度*该循环运行的次数。
常见的时间复杂度:
O(1)< O(logn)
o(n^3)之后过大的n会导致非常可怕的运算时间,所以一般不考虑。
最坏情况:运行时间最坏情况,一般说时间复杂度说的就是这个。
平均时间:平均运行时间,期望中的运行时间。
算法空间复杂度:s(n)= o(f(n)),f(n)是语句关于问题规模n的所占存储空间的函数。
定义:0个or多个数据元素的有限序列,序列是指数据元素之间是有顺序的。
线性表的顺序存储结构:用连续的地址存储单元来依次存储数据元素。
List,python中直接存在的一个数据抽象类型。
操作有:初始化、插入、删除、查询、长度计算、清空等等。
删除的两种方式:list.remove(你要删除的具体元素的值) 、del[index]。
清空:list.clear()
查找操作知道地址(索引值)即可,时间复杂度o(1),但是插入和删除平均时间复杂度为o(n),因为每次都要移动大量数据。当线性表长度变化较大的时候,难以确定存储空间的容量。
线性表的链式存储结构:使用指针指向下一个数据节点。
操作有:初始化、插入、删除、查询(通过数值查询,通过索引查询)、长度计算、清空、倒转、合并等等。
链表中数据元素存储的方式是通过节点。
à头结点à第一个节点à……à尾结点à
指向头结点的指针叫做头指针,头结点一般数据域不放数值,或者存放链表的长度等信息,尾节点尾指针指向为空。头结点不是链表的必须要素,但是头结点的存在可以使第一节点插入和删除的与其他节点操作统一。
链表有四大类:单链表、循环、双向、静态链表(上一个数据节点存放了下一个数据的索引值,用数组来构成链表)。
下面是没有头结点的链表的python实现。
class linked_list:
def __init__(self,value):
self.next = None
self.val = value
def search_by_index(self,head,index):
print('传值过来的位置:',head)
if head == None or index < 0:
return None
for i in range(index):
head = head.next
print('改变后head的位置:', head)
if head == None:
return None
return head
def search_by_value(self,head,value):
if head == None or value == None:
return None
while head != None:
if head.val == value:
return head
else:
head = head.next
return None
def add(self,head,node_code,index):
if index == 0:
new_head = node_code
new_head.next = head
return new_head
else:
pre_node = self.search_by_index(head,index-1)
if pre_node == None:
return None
node_code.next = pre_node.next
pre_node.next = node_code
return head
def delete(self,head,index):
if head == None or index < 0:
return None
if index == 0:
return head.next
pre_node = self.search_by_index(head,index-1)
print('寻找到的pre_node的位置:',pre_node)
pre_node.next = pre_node.next.next
print(head)
return head
def count(self,head):
if head == None:
return None
count = 0
while head:
count += 1
head = head.next
return count
def merge(self,head1,head2):
dummy_node = linked_list(0)
new_head = dummy_node
cur = dummy_node
while head1 != None and head2 != None:
if head1.val <= head2.val:
cur.next = head1
head1 = head1.next
else:
cur.next = head2
head2 = head2.next
cur = cur.next
if head1 != None:
cur.next = head1
if head2 != None:
cur.next = head2
return new_head.next
def reverse(self,head):
if head == None:
return None
if head.next == None:
return head
pre = None
while head:
cur = head.next #保留下一个元素
head.next = pre #把元素的指针域改变,指向前一个
pre = head #把下一个元素的next变为pre
head = cur #把一下个元素变为当前元素
return pre
def print_head(self,head):
while head:
print(head.val)
head = head.next
顺序存储和链式存储的对比:
存储方式不同:
顺序存储是在连续的基本存储单元上存储的,逻辑关系等于物理关系。链式存储是在任意存储空间上进行存储的,用指针来指向下一个存储单元。
时间性能:
链式存储在查找时需要o(n)的时间复杂度,但是顺序存储只需要o(1)(利用索引),所以如果要进行非常多次的查询操作时,可以使用顺序存储。比如游戏用户注册信息,只有注册和注销时会删除和新增,大多数时候都是查询。
而在添加和删除的时候,链表首先查找到元素后只需要o(1)的操作,而顺序存储则因为需要移动大量数据所以需要o(n)的操作。所以需要非常多插入和删除操作时,链表更加合适。比如游戏装备列表,
空间性能:
顺序存储需要预分配存储空间,容易浪费或者是溢出,链表的元素个数就不受限制。