我们先来看四种简单但是重要的数据结构:栈(stack)、队列(queue)、双端队列(deque)、 列表(list)。它们的区别在于元素如何加入集合中和从集合中移除。这些数据结构,也称为线性数据结构。
一、 栈
1. 排序原则:后进先出(LIFO)
从图中可以看出,第一个被加入栈中的元素[4]处于栈的底端,需要上面的元素[8.4]、[True]、['dog']先出去,[4]才能出去。
2. 实现抽象的栈数据结构
一个栈stack应该要有以下的函数实现其功能
1)Stack() 创建一个空的栈。不需要参数、并返回一个空的栈。
2)Push(item) 增添一个新的元素到栈的顶端。需要增添的新元素作为参数,无返回值。
3)Pop( ) 从栈的顶端移除一个元素。不需要参数,并返回被移除的元素。
4) Peek( ) 返回栈的顶端的元素,但不移除它。不需要参数。
5) is_empty( ) 查看栈是否为空。不需要参数,返回布尔值。
6)size( ) 返回栈中元素的数目。不需要参数,返回一个整数。
class Stack:
def __init__(self):
self.items = [] # 使用Python里的List作为栈容器
def is_empty(self):
return self.items == []
def push(self,item):
self.items.append(item) # list.append(x) 添加元素到列表末端 Add an item to the end of the list
def pop(self):
return self.items.pop() # list.pop( ) 移除和返回列表中最后一个元素 Removes and returns the last item in the list
def peek(self):
return self.items[len(self.items)-1]
def size(self):
return len(self.items)
下面是对这个类的测试效果
3. 例子:十进制整数转换为二进制至十六进制整数
我们首先来看十进制转换为二进制的原理,如将10转换为二进制则是1010,转换过程为
10//2 = 5 rem(余数)= 0
5//2 = 2 rem = 1
2//2 = 1 rem = 0
1//2 = 0 rem = 1
取反则为1010
这个过程和我们栈的结构相近,我们可用栈按照0101的顺序储存余数,然后按后进先出取出,则为1010.
import stack
def base_converter(dec_number,base):
digits="0123456789ABCDEF" # 用来查找替代的数值
rem_stack = stack.Stack() # 新建栈
while dec_number>0:
rem = dec_number % base # 求余,如5%2=1
rem_stack.push(rem) # 储存余数
dec_number = dec_number // base # 除法向下取整,如5//2=2
new_string = ""
while not rem_stack.is_empty():
# 按后进先出的顺序取出栈中的元素,如取出的元素为15,则替代为十六进制中的F
new_string = new_string +digits[rem_stack.pop()]
return new_string
print(base_converter(10,2))
print(base_converter(25,16))
二、 队列(Queue)
1. 排序原则:先进先出(FIFO)
从图中可以看出,队列就像是我们现实生活中的等车排队一样,第一个来排队的人第一个上车。
2. 实现抽象的队列数据结构
一个队列Queue应该要有以下的函数实现其功能:
1) Queue( ) 创建一个空的队列。不需要参数、并返回一个空的队列。
2)enqueue(item) 增添一个新的元素到队列后面。需要增添的新元素作为参数,无返回值。
3)dequeue( ) 移除队列前面的元素。不需要参数,并返回被移除的元素。
4) is_empty( ) 查看队列是否为空。不需要参数,返回布尔值。
5)size( ) 返回队列中元素的数目。不需要参数,返回一个整数。
我们现在用Python来完善一个具备上述功能的Queue类:
class Queue:
def __init__(self):
self.items = []
def is_empty(self):
return self.items == []
def enqueue(self,item):
# list.insert(i, x) Insert an item at a given position.
self.items.insert(0,item) # 添加元素到列表前端
def dequeue(self):
return self.items.pop()
def size(self):
return len(self.items)
下面是对这个类的测试效果
3. 例子:击鼓传花
我们小时候想必都玩过一种叫做“击鼓传花”的游戏:所有人围成一个圈,鼓点开始的时候,花从其中一个人手里传到下一个人手里,当鼓点停的时候,手里有花的玩家会被淘汰,然后剩下的人继续比赛。我们就使用队列的结构来模拟这样的游戏。
游戏:我们有六个人,每计数1次,队列最前面的人排到队列最后面,鼓点(计数7次)停止时,新队列最前面的人被淘汰。一直到剩下一个人为止。
import queue
def hot_potato(name_list,num):
sim_queue = queue.Queue()
for name in name_list:
sim_queue.enqueue(name)
while sim_queue.size()>1:
for i in range(num):
sim_queue.enqueue(sim_queue.dequeue()) # 传递7次以后组成新的队列
print(sim_queue.dequeue()) # 手里有花的人被淘汰
return sim_queue.dequeue() #返回剩下的人
print(hot_potato(['Bill','David','Susan','Jane','Kent','Brad'],7))
三、 双端队列(Deque)
1. 排序原则:两端进出
从图中可以看出,双端队列比队列要灵活,可以从前面也可以从后面进行添加、删除等操作。
2. 实现抽象的双端队列数据结构
一个队列Deque应该要有以下的函数实现其功能:
1)Deque( ) 创建一个空的双端队列。不需要参数,并返回一个空的双端队列。
2)add_front(item) 增添一个新的元素到双端队列前面。需要增添的新元素作为参数,无返回值。
3)remove_front ( ) 移除双端队列前面的元素。不需要参数,并返回被移除的元素。
4)remove_rear ( ) 移除双端队列后面的元素。不需要参数,并返回被移除的元素。
5)is_empty( ) 查看双端队列是否为空。不需要参数,返回布尔值。
6)size( ) 返回双端队列中元素的数目。不需要参数,返回一个整数。
我们现在用Python来完善一个具备上述功能的Dueue类:
class Deque:
def __init__(self):
self.items = []
def is_empty(self):
return self.items == []
def add_front(self,item):
self.items.append(item)
def add_rear(self,item):
self.items.insert(0,item)
def remove_front(self):
return self.items.pop()
def remove_rear(self):
return self.items.pop(0)
def size(self):
return len(self.items)
下面是对这个类的测试效果
3. 例子:回文(指顺读和倒读都一样的词语)
有些英文单词如radar,toot,madam无论顺读还是倒读都一样,正如一些汉语词汇如“我为人人,人人为我”一样。我们使用双端队列来实现回文的检测。
import deque
def pal_checker(a_string):
char_deque = deque.Deque()
for ch in a_string:
char_deque.add_rear(ch) # 拆分字符串放入双端队列中
still_equal = True
while char_deque.size()>1 and still_equal:
first = char_deque.remove_front() # 去掉双端队列前面的一个元素
last = char_deque.remove_rear() # 去掉双端队列后面的一个元素
if first != last: # 判断去掉的前后元素是否相同
still_equal = False
return still_equal
print(pal_checker('helloworld'))
print(pal_checker('radar'))
print(pal_checker('少生孩子多种树'))
print(pal_checker('我为人人,人人为我'))
四、 无序列表——链表(Linked Lists)
1. 排序原则:无序,但是有相对位置
从图中可以看出,无序列表具有相对位置,[54]是head,[31]是end, 但是在物理层它们的位置没有顺序的限制。无序列表和前三种数据结构最大的不同在于
可以往无序列表中的任意位置添加和删除元素
。
2. 一个无序列表List应该要有以下的函数实现其功能:
1)List( ) 创建一个空的列表。不需要参数,并返回一个空的列表。
2)add(item) 增添一个新的元素到列表。需要增添的新元素作为参数,无返回值。
3)remove(item) 移除列表中的元素。
需要参数。
假设元素已经存在于列表中。
4)search (item) 查找列表中的元素。
需要参数
,并返回布尔值。
5)is_empty( ) 查看列表是否为空。不需要参数,返回布尔值。
6)size( ) 返回列表中元素的数目。不需要参数,返回一个整数。
7)append(item) 在列表末尾增加一个新的元素作为集合中的最后一个元素。需要参数,无返回值。假设元素未存在于列表中。
8)index(item) 返回列表中元素的位置。需要参数,返回一个索引号。假设元素已经存在于列表中。
9)insert(pos,item) 在列表中的指定位置插入。需要参数,无返回值。假设元素未存在于列表中。
10)pop( ) 移除列表中末尾的。不需要参数,返回被移除的元素。假设列表至少有一个元素。
11)pop(pos) 移除列表中指定位置的元素。需要参数,返回被移除的元素。假设列表中存在该元素。
我们现在用Python来完善一个具备上述功能的UnorderedList类, 在此之前,我们先实现一个Node(节点)类:
class Node:
def __init__(self,init_data):
self.data = init_data
self.next = None
def get_data(self):
return self.data
def get_next(self):
return self.next
def set_data(self, new_data):
self.data = new_data
def set_next(self, new_next):
self.next = new_next
temp = Node(93)
print(temp.get_data())
Node类的原理如下图所示
Node由两部分组成,一部分是当前位置的元素data,另一部分是指向下一个位置元素的next(类似C语言中的指针)。
为何我们会先引入Node类?因为Node类是构成UnorderedList类的基本元素。正因为Node有指向下一个位置的部分,只要我们将next指向的位置改变,就可以修改列表中的任意元素。比如,原本93指向的下一个元素是4,我们只需要将93指向5,然后5指向4,这样就可以在93和4之间添加一个新元素5。这就是链表结构的奇妙之处。
接下来我们用Python来完善UnorderedList类:
首先,我们的链表要有一个首元素,在链表内还没有元素的时候,首元素为空。
class UnorderedList:
def __init__(self):
self.head = None
其次,一个判断是否空链表的函数,只需要检查首元素。
def is_empty(self):
return self.head == None
再次,一个为链表添加元素的函数。这里用到我们的之前定义的Node类,正如我们之前所举的例子:原本93指向的下一个元素是4,我们只需要将93指向5,然后5指向4,这样就可以在93和4之间添加一个新元素5。
这里我们只列出add函数,从首元素的位置为链表增添元素。若想要实现从指定位置插入元素,则可以按照相同的原理实现insert函数。
def add(self,item):
temp = node.Node(item)
temp.set_next(self.head)
self.head = temp
然后,我们定义一个search函数实现查找功能。这里的查找方式为最常见的顺序查找法。
def search(self,item):
current = self.head
found = False
while current != None and not found:
if current.get_data() == item:
found = True
else:
current = current.get_next()
return found
最后我们定义一个remove函数实现删除元素的功能。删除分为两部分,一部分是查找,另一部分是删除。删除类似于添加,如要删除93,5,4中的5,则需要将93指向5改成93指向4。
def remove(self,item):
current = self.head
previous = None
found = False
while not found:
if current.get_data() == item:
found = True
else:
previous = current
current = current.get_next()
if previous == None: # 如果删除的是首元素,则设置下一个元素为首元素
self.head = current.get_next()
else: # 如果删除的不是首元素,则前一个元素指向后一个元素(跳过了被删除的当前元素)
previous.set_next(current.get_next())
到目前为止,我们基本实现了简单的链表结构。
五、 有序列表 (Ordered Lists)
1. 排序原则:数值按照从大到小或者从小到大的顺序在列表中排列
从图中可以看出,有序列表和无序列表相近,所不同的是列表中的元素按照一定的顺序排列。
2. 实现一个简单的Ordered List类
有序列表和无序列表的实现相近,但是在实现查找功能有微略不同。比如一个升序列表,查找5时,当查找到第一个大于5的元素时就停止了,因为后面的元素均大于5。因此,有序列表的好处在于其查找速度较快。
但是,有序列表添加元素的速度较慢。试想一下我们往一个有序列表[1,2,4,5]中添加3,则需要查找到4的位置,然后将2指向3,3指向4。与无序列表往首元素开始添加多了一个查找的过程。
class OrderedList:
def __init__(self):
self.head = None
def search(self,item):
current = self.head
found = False
stop = False
while current != None and not found and not stop:
if current.get_data() == item:
found = True
else:
if current.get_data() > item:
stop = True
else:
current = current.get_next()
return found
def add(self, item):
current = self.head
previous = None
stop = False
while current != None and not stop:
if current.get_data()>item:
stop = True
else:
previous = current
current = current.get_next()
temp = node.Node(item)
if previous == None:
temp.set_next(self.head)
self.head = temp
else:
temp.set_next(current)
previous.set_next(temp)
参考资料:Problem Solving with Algorithms and Data Structures