程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

Data structure Python Version (IV) - queue

編輯:Python

Catalog

  • One 、 Introduction to queue
  • Two 、 Sequential queue
    • 2.1 Acyclic queues
    • 2.2 Circular queue
      • 2.2.1 False spillover
      • 2.2.2 The structure of circular queue
      • 2.2.3 Implementation of circular queue
  • 3、 ... and 、 The chain team
    • 3.1 Complete realization of chain team
  • Four 、 deque
    • 4.1 Create a two terminal queue
    • 4.2 Sentenced to empty
    • 4.3 The main method
  • 5、 ... and 、 Priority queue
  • 6、 ... and 、 Stack \leftrightarrow queue
    • 6.1 Using queue to implement stack
      • Method 1 : Dual queue implementation
      • Method 2 : Single queue implementation
    • 6.2 Using stack to realize queue
  • 7、 ... and 、LeetCode actual combat
    • 7.1 LeetCode#387—— The first unique character in the string
      • Method 1 : queue
      • Method 2 : Hashtable

One 、 Introduction to queue

Queues are Operations Limited The linear table , It is limited to allowing insertion only at one end of the table , Delete at the other end of the table . The end of the insertion is called A party (rear), The end of deletion is called Team head or Team leader (front). Inserting elements into a queue is called Enter the team or The team , Deleting an element from a queue is called Out of the team or Leave the team .

A distinguishing feature of queues is :FIFO(First In First Out).

Queues need to be implemented in the same way as stacks :empty()push(e)pop() and gethead().

Two 、 Sequential queue

The queue with sequential storage structure is called Sequential team . We use lists data To store the elements in the queue , Set two more pointers , The team head pointer is front, The tail pointer is rear.

For the sake of simplicity , Here is a list of fixed capacities :

front Pointing to the team head element Previous Location , and rear Just right Point to the end element .

The sequential team is divided into Acyclic queues and Circular queue Two structures , Let's first discuss acyclic queues , And by explaining the shortcomings of this type of queue, it leads to the circular queue .

2.1 Acyclic queues


Initial time setting front and read Are all -1, The four elements of an acyclic queue are as follows :

  • Team air condition :front == rear;
  • Team full conditions :rear = MaxSize - 1;
  • Elements e e e Team operation :rear increase 1, And will e e e Put it in that position ( The elements that enter the team are always inserted at the end );
  • Out of line operation :front increase 1, Take out the element in this position ( The element of leaving the team always comes out at the head of the team ).

The structure of acyclic queue is as follows :

class SqQueue:
def __init__(self):
self.__MaxSize = 100
self.data = [None] * self.__MaxSize
self.front = self.rear = -1

Complete implementation :

class SqQueue:
def __init__(self):
self.__MaxSize = 100
self.data = [None] * self.__MaxSize
self.front = self.rear = -1
def empty(self):
return self.front == self.rear
def push(self, e):
assert self.rear != self.__MaxSize - 1
self.rear += 1
self.data[self.rear] = e
def pop(self):
assert not self.empty()
self.front += 1
return self.data[self.front]
def gethead(self):
assert not self.empty()
return self.data[self.front + 1]

The time complexity of the above operations is O ( 1 ) \mathcal{O}(1) O(1).

2.2 Circular queue

2.2.1 False spillover

In an acyclic queue , When the element enters the team, the pointer at the end of the team rear += 1, When elements are out of the team front += 1. When the team is full , namely rear = MaxSize - 1 When established , At this time, even if there are still several elements in the team , All that changed was front The location of , and rear unchanged , Even if the team meets the conditions, it still holds . This kind of up overflow with empty positions in the queue but still meeting the condition of full queue is called False spillover . in other words , There is a false overflow phenomenon in the acyclic queue .

In order to overcome false overflow , Make full use of the storage space of the array , We can data Connect the front and back ends of the array , Form a circular array , That is, the table storing queue elements is logically regarded as a ring , be called Circular queue .

2.2.2 The structure of circular queue


The circular queue is end-to-end , When the tail pointer rear = MaxSize - 1 when , One more position and you'll arrive 0, This can be achieved by using the remainder operation :

  • Pointer to the first team :front = (front + 1) % MaxSize;
  • Pointer to a party :rear = (rear + 1) % MaxSize.

The queue head pointer and queue tail pointer of the circular queue initially point to 0( The real location , Unlike the acyclic queue, the queue head pointer and the queue tail pointer initially point to the virtual location -1). When there are elements in the team ,rear += 1, When there are elements out of the team ,front += 1.

A detail can be found from the above figure : When the team is empty and full rear == front, Then how should we judge the team's empty ? In fact, we need to add one constraint , As follows .

Both circular queues and acyclic queues need to pass front and rear To identify the queue status . We know , A length of m m m There are the following queues m + 1 m+1 m+1 States :

  • Team space ;
  • There is an element in the team ;
  • There are two elements in the team ;
  • ⋯ \cdots ⋯;
  • In the team m m m Elements ( That is, the team is full ).

If taken |front - rear| To identify the status of the circular queue , because front and rear The value range of is 0 ∼ m − 1 0\sim m-1 0∼m−1, thus |front - rear| Only m m m Species value . be aware m < m + 1 m<m+1 m<m+1, So if you use |front - rear| To distinguish the queue status , There must be Two kinds of Status cannot be distinguished . So if we limit the length to m m m The queue of contains at most m − 1 m-1 m−1 One element , You can use |front - rear| To distinguish all queue States .

Why are there two states that cannot be distinguished instead of one ? We can consider the simplest case , Suppose the queue length is 1 1 1, So there are only two states : Team space , There is only one element in the team ( That is, the team is full ). here |front - rear| There is only one value : 0 0 0, We cannot judge the specific status of the queue by this value .

It is specified that the circular queue can contain at most m − 1 m-1 m−1 After elements , We can use rear == front To judge that the team is empty . When the team is full ( That is, the queue contains m − 1 m-1 m−1 Elements ), There must be (rear + 1) % MaxSize == front. So we get the four elements of the circular queue :

  • Team air condition :rear == front;
  • Team full conditions :(rear + 1) % MaxSize == front;
  • Elements e e e Team operation :rear = (rear + 1) % MaxSize, then e e e Put it in that position ;
  • Out of line operation :front = (front + 1) % MaxSize, Take out the element in this position .

2.2.3 Implementation of circular queue

The complete implementation is as follows :

class CSqQueue:
def __init__(self):
self.__MaxSize = 100
self.data = [None] * self.__MaxSize
self.front = self.rear = 0
def empty(self):
return self.front == self.rear
def push(self, e):
assert (self.rear + 1) % self.__MaxSize != self.front
self.rear = (self.rear + 1) % self.__MaxSize
self.data[self.rear] = e
def pop(self):
assert not self.empty()
self.front = (self.front + 1) % self.__MaxSize
return self.data[self.front]
def gethead(self):
assert not self.empty()
return self.data[(self.front + 1) % self.__MaxSize]

The time complexity of the above operations is O ( 1 ) \mathcal{O}(1) O(1).

3、 ... and 、 The chain team

The chained storage structure of queues is abbreviated as The chain team . It is realized by a single linked list composed of nodes . At this time, only the header of the single linked list can be deleted , Insert at the end of the single linked list .

It should be noted that , The single linked list here does not take the lead , And you need two pointers to identify the single linked list . among front Point to the head of the team ,rear Point to the end of the line .

The basic structure is as follows :

class LinkNode:
def __init__(self, data=None, next=None):
self.data = data
self.next = next
class LinkQueue:
def __init__(self):
self.front = None
self.rear = None

The four elements of the chain team are as follows :

  • Team air condition :front == rear == None, Just front == None As a team air condition ;
  • Team full conditions : The queue will be full only when the memory overflows , So don't consider ;
  • Elements e e e Team operation : Insert a storage at the end of the single linked list e e e The node of , And let rear Pointing to it ;
  • Out of line operation : Take out the first node of the team data Value and delete it from the chain .

3.1 Complete realization of chain team

class LinkQueue:
def __init__(self):
self.front = None
self.rear = None
def empty(self):
return self.front == None
def push(self, e):
s = LinkNode(e)
if self.empty():
self.front = self.rear = s
else:
self.rear.next = s
self.rear = s
def pop(self):
assert not self.empty()
if self.front == self.rear:
e = self.front.data
self.front = self.rear = None
else:
e = self.front.data
self.front = self.front.next
return e
def gethead(self):
assert not self.empty()
return self.front.data

Four 、 deque

deque (double-ended queue, abbreviation deque) It is extended from the queue :

Double ended queues are the same as queues , The logical relationship of elements is also linear , But the queue can only enter at one end , The other end of the line . The double ended queue can enter and leave the queue at both ends , It has the characteristics of queue and stack , More flexible to use .

It is also very simple to implement a double ended queue from scratch , Omit here . in fact Python Such a data structure is provided in :

from collections import deque

4.1 Create a two terminal queue

Directly create an empty double ended queue :

q = deque()

q It's empty , It is a dynamically scalable double ended queue .

Create a fixed length double ended queue :

q = deque(maxlen=10)

here q It's empty , But the maximum length is 10. When a new element is added and the double ended queue is full , The oldest element will be automatically removed .

Create a double ended queue from the list :

a = [1, 2, 3, 4, 5]
q = deque(a)
print(q)
# deque([1, 2, 3, 4, 5])

4.2 Sentenced to empty

deque There is no method to judge the air , have access to len(q) == 0 To determine whether the double ended queue is empty , The time complexity is zero O ( 1 ) \mathcal{O}(1) O(1).

4.3 The main method

Method effect deque.clear() Clear all elements in the queue deque.append(x) In the line Right Add elements at the end x x x, O ( 1 ) \mathcal{O}(1) O(1)deque.appendleft(x) In the line Left Add elements at the end x x x, O ( 1 ) \mathcal{O}(1) O(1)deque.pop() In the line Right Bring out an element of the team , O ( 1 ) \mathcal{O}(1) O(1)deque.popleft() In the line Left Bring out an element of the team , O ( 1 ) \mathcal{O}(1) O(1)deque.extend(L) In the line Right End add list L L L The elements of deque.extendleft(L) In the line Left End add list L L L The elements of deque.remove(x) Delete the first and x x x Matching elements ( Match from left ), O ( n ) \mathcal{O}(n) O(n)deque.count(x) Statistics x x x The number of , O ( n ) \mathcal{O}(n) O(n)deque.reverse() Reverse queue deque.rotate(n) To the right n n n A place . if n n n It's a negative number , Move left

Some examples :

q = deque([1, 2, 3, 4, 5])
q.append(6)
q.appendleft(0)
print(q)
# deque([0, 1, 2, 3, 4, 5, 6])
q.pop()
q.popleft()
print(q)
# deque([1, 2, 3, 4, 5])
q.extend([1, 2, 3])
q.extendleft([7, 8, 9])
print(q)
# deque([9, 8, 7, 1, 2, 3, 4, 5, 1, 2, 3])
q.remove(1)
print(q)
# deque([9, 8, 7, 2, 3, 4, 5, 1, 2, 3])
print(q.count(2))
# 2
q.reverse()
print(q)
# deque([3, 2, 1, 5, 4, 3, 2, 7, 8, 9])
q.rotate(3)
print(q)
# deque([7, 8, 9, 3, 2, 1, 5, 4, 3, 2])
q.clear()
print(q)
# deque([])

5、 ... and 、 Priority queue

The so-called priority queue , Is to specify the priority of the elements in the queue , The higher the priority, the more priority the element will be out of the team . Ordinary queue can be regarded as a special priority queue , The earlier you enter the team, the higher the priority .

Priority queues are usually implemented with heaps , Subsequent articles in this series will introduce , I won't mention it here .

6、 ... and 、 Stack \leftrightarrow queue

6.1 Using queue to implement stack

Method 1 : Dual queue implementation

class MyStack:
def __init__(self):
self.q1 = collections.deque()
self.q2 = collections.deque() # Similar to temporary variables temp The role of 
def push(self, x: int) -> None:
self.q2.append(x)
while self.q1:
self.q2.append(self.q1.popleft())
self.q1, self.q2 = self.q2, self.q1
def pop(self) -> int:
return self.q1.popleft()
def top(self) -> int:
return self.q1[0]
def empty(self) -> bool:
return not self.q1

Method 2 : Single queue implementation

class MyStack:
def __init__(self):
self.q = collections.deque()
def push(self, x: int) -> None:
n = len(self.q)
self.q.append(x)
for _ in range(n):
self.q.append(self.q.popleft())
def pop(self) -> int:
return self.q.popleft()
def top(self) -> int:
return self.q[0]
def empty(self) -> bool:
return not self.q

The time complexity and space complexity of the above two methods are the same .

6.2 Using stack to realize queue

We just need to treat the list as a stack , Then use two stacks to realize .

class MyQueue:
def __init__(self):
self.s1 = []
self.s2 = []
self.front = None
def push(self, x: int) -> None:
if not self.s1: self.front = x
self.s1.append(x)
def pop(self) -> int:
if not self.s2:
while self.s1:
self.s2.append(self.s1.pop())
self.front = None
return self.s2.pop()
def peek(self) -> int:
return self.s2[-1] if self.s2 else self.front
def empty(self) -> bool:
return True if not self.s1 and not self.s2 else False

7、 ... and 、LeetCode actual combat

7.1 LeetCode#387—— The first unique character in the string

Method 1 : queue

We can create a dictionary and a queue . The dictionary is used to count the index of each character , For those repeating characters , The dictionary will set its index to − 1 -1 −1.

When traversing a string , Let's first check whether the current character has appeared in the dictionary . If not , Add the character and its corresponding index to the dictionary and queue at the same time ( Join the queue in the form of tuples ). If there has been , We will set the index of this character in the dictionary to − 1 -1 −1, At the same time, constantly check the team head elements , As long as its index in the dictionary is − 1 -1 −1 Just pop up . The final team head element is the first non repeating character we need .

Of course , Team space time , Represents that there are no non repeating characters .

class Solution:
def firstUniqChar(self, s: str) -> int:
hashmap = dict()
q = collections.deque()
for i in range(len(s)):
if s[i] not in hashmap:
hashmap[s[i]] = i
q.append((s[i], i))
else:
hashmap[s[i]] = -1
while q and hashmap[q[0][0]] == -1:
q.popleft()
return -1 if not q else q[0][1]

Method 2 : Hashtable

This method is simple and intuitive .

class Solution:
def firstUniqChar(self, s: str) -> int:
freq = collections.Counter(s)
for i in range(len(s)):
if freq[s[i]] == 1:
return i
return -1

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved