python基础--数据结构

参考

1、列表

1.1 Python中的顺序表

Python中的list和tuple两种类型采用了顺序表的实现技术,具有前面讨论的顺序表的所有性质。
tuple是不可变类型,即不变的顺序表,因此不支持改变其内部状态的任何操作,而其他方面,则与list的性质类似。
Python标准类型list就是一种元素个数可变的线性表,可以加入和删除元素,并在各种操作中维持已有元素的顺序(即保序),而且还具有以下行为特征:
基于下标(位置)的高效元素访问和更新,时间复杂度应该是O(1);

  1. 为满足该特征,应该采用顺序表技术,表中元素保存在一块连续的存储区中。
  2. 允许任意加入元素,而且在不断加入元素的过程中,表对象的标识(函数id得到的值)不变。
  3. 为满足该特征,就必须能更换元素存储区,并且为保证更换存储区时list对象的标识id不变,只能采用分离式实现技术。

在Python的官方实现中,list就是一种采用分离式技术实现的动态顺序表。这就是为什么用list.append(x) (或 list.insert(len(list), x),即尾部插入)比在指定位置插入元素效率高的原因。
在Python的官方实现中,list实现采用了如下的策略:在建立空表(或者很小的表)时,系统分配一块能容纳8个元素的存储区;在执行插入操作(insert或append)时,如果元素存储区满就换一块4倍大的存储区。但如果此时的表已经很大(目前的阀值为50000),则改变策略,采用加一倍的方法。引入这种改变策略的方式,是为了避免出现过多空闲的存储位置。

2、链表

2.1单向链表

# -*- coding: utf-8 -*-
"""
Created on Wed Feb 24 21:32:24 2021

@author: daicong
"""


"""
is_empty()
length()
tavel()
add(item)
append(item)
insert(pos,item)
remove(pos,item)
search(item)

"""
class SingleNode(object):
    def __init__(self,item):
        self.item = item
        self.next = None
        
class SingleLinkList(object):
    def __init__(self):
        self.__head = None
    
    def is_empty(self):
        """判断链表是否为空"""
        return self.__head == None
    
    def length(self):
        """打印链表长度"""
        count = 0
        cur = self.__head
        while cur != None:
            count += 1
            cur = cur.next
        return count
    
    def travel(self):
        """遍历整个链表"""
        cur = self.__head
        while cur != None:
            print(cur.item)
            cur = cur.next
            
    def add(self,item):
        """
        链表首部添加元素
        首先将元素实例化为node
        """
        node = SingleNode(item)
        """插入操作,应该是先将node指向首节点,然后node再成为首节点"""
        node.next =self.__head
        self.__head = node
        
    def append(self,item):
        """尾部添加元素"""
        node = SingleNode(item)
        cur = self.__head
        if self.is_empty():
            cur = node
        else:
            while cur.next != None:
                cur = cur.next
            cur.next = node
        
        
    def insert(self,pos,item):
        """在添加元素"""
        if pos <= 0:
            self.add(item)
        elif pos > self.length()-1:
            self.append(item)
        else:
            node = SingleNode(item)
            pre = self.__head
            count = 0
            while count < pos-1:
                """找到插入点的前一个节点,插入到该节点的后面"""
                pre = pre.next
                count += 1
            node.next = pre.next
            pre.next = node
                    
    def remove(self,item):
        """删除元素"""
        cur =self.__head
        pre = None
        """找到该元素之前的元素,再进行删除操作"""
        while cur != None:
            if cur.item == item:
                if not pre :
                    """如果为第一个节点"""
                    self.__head = cur.next
                    return
                pre.next = cur.next
                return
            pre = cur
            cur = cur.next               
            
    def research(self,item):
        """查找元素"""
        cur =self.__head
        while cur != None:
            if cur.item == item:
                return True
            cur = cur.next
        return False

if __name__ == "__main__":
    ll = SingleLinkList()
    ll.add(1)
    ll.add(2)
    ll.append(3)
    ll.insert(2, 4)
    print( "length:",ll.length())
    ll.travel()
    print(ll.research(3))
    print(ll.research(5))
    ll.remove(1)
    ll.remove(2)
    print("length:",ll.length())
    ll.travel()
"""
length: 4
2
1
4
3
True
False
length: 2
4
3
"""

2.2单向循环链表

  • 单链表的一个变形是单向循环链表,链表中最后一个节点的next域不再为None,而是指向链表的头节点
# -*- coding: utf-8 -*-
"""
Created on Wed Feb 24 23:36:51 2021

@author: daicong
"""

class SingleNode(object):
    def __init__(self,item):
        self.item = item
        self.next = None
        
class SingleCycleLinkList(object):
    def __init__(self):
        self.__head = None
    
    def is_empty(self):
        """判断链表是否为空"""
        return self.__head == None
    
    def length(self):
        """打印链表长度"""
        if self.is_empty():
            return 0
        count = 0
        cur = self.__head
        while cur.next != self.__head:
            count += 1
            cur = cur.next
        return count + 1
    
    def travel(self):
        """遍历整个链表"""
        if self.is_empty():
            return
        cur = self.__head
        while cur.next != self.__head:
            """打印除了最后一个节点以外的节点的元素"""
            print(cur.item, end=" ")
            cur = cur.next
        """打印最后一个节点的元素"""
        print(cur.item)
            
    def add(self,item):
        """
        链表首部添加元素
        首先将元素实例化为node
        """
        node = SingleNode(item)
        if self.is_empty():
            self.__head = node
            node.next = self.__head
        else:               
            """插入操作,应该是先将node指向首节点,然后node再成为首节点"""
            node.next =self.__head
            cur = self.__head
            while cur.next != self.__head:
                """寻找尾部节点"""
                cur = cur.next
            """尾节点指向首节点"""
            cur.next = node
            self.__head = node
        
    def append(self,item):
        """尾部添加元素"""
        node = SingleNode(item)
        cur = self.__head
        if self.is_empty():
            cur = node
            node.next = cur
        else:
            while cur.next != self.__head:
                cur = cur.next
            cur.next = node
            node.next = self.__head
        
        
    def insert(self,pos,item):
        """在pos添加元素"""
        if pos <= 0:
            self.add(item)
        elif pos > self.length()-1:
            self.append(item)
        else:
            node = SingleNode(item)
            pre = self.__head
            count = 0
            while count < pos-1:
                """找到插入点的前一个节点,插入到该节点的后面"""
                pre = pre.next
                count += 1
            node.next = pre.next
            pre.next = node
                    
    def remove(self,item):
        """删除元素"""
        if self.is_empty():
            return
        cur =self.__head
        pre = None
        """如果是首节点"""
        if cur.item == item:
            """pre记录首节点"""
            pre = cur
            while cur.next != self.__head:
                """找到尾节点"""
                cur = cur.next
            """移动尾节点"""
            cur.next = pre.next
            """移动首节点"""
            self.__head = pre.next
            return
        
        """如果是中间节点"""
        while cur.next != self.__head:
            if cur.item == item:
                pre.next = cur.next
                return
            pre = cur
            cur = cur.next
        """如果是尾节点"""
        pre.next = cur.next
        return
            
    def research(self,item):
        """查找元素"""
        cur =self.__head
        while cur.next != self.__head:
            if cur.item == item:
                return True
            cur = cur.next
        return True if cur.item == item else False

if __name__ == "__main__":
    ll = SingleCycleLinkList()
    ll.add(1)
    ll.add(2)
    ll.append(3)
    ll.travel()
    ll.insert(2, 4)
    ll.insert(4, 5)
    ll.insert(0, 6)
    print("length:",ll.length())
    ll.travel()
    print( ll.research(3))
    print( ll.research(7))
    ll.remove(1)
    print( "length:",ll.length())
    ll.travel()
    ll.remove(6)
    ll.remove(5)
    print( "length:",ll.length())
    ll.travel()
    print( ll.research(3))
"""
2 1 3
length: 6
6 2 1 4 3 5
True
False
length: 5
6 2 4 3 5
length: 3
2 4 3
True
"""

2.3双向链表

  • 一种更复杂的链表是“双向链表”或“双面链表”。每个节点有两个链接:一个指向前一个节点,当此节点为第一个节点时,指向空值;而另一个指向下一个节点,当此节点为最后一个节点时,指向空值。
# -*- coding: utf-8 -*-
"""
Created on Thu Feb 25 10:13:19 2021

@author: daicong
"""

class SingleNode(object):
    def __init__(self,item):
        self.item = item
        self.next = None
        self.prev = None
        
        
class DooubleLinkList(object):
    def __init__(self):
        self.__head = None
    
    def is_empty(self):
        """判断链表是否为空"""
        return self.__head == None
    
    def length(self):
        """打印链表长度"""
        if self.is_empty():
            return 0
        count = 0
        cur = self.__head
        while cur != None:
            count += 1
            cur = cur.next
        return count
    
    def travel(self):
        """遍历整个链表"""
        if self.is_empty():
            return
        cur = self.__head
        while cur != None:
            """打印所有节点的元素"""
            print(cur.item, end=" ")
            cur = cur.next
        print("")
            
    def add(self,item):
        """
        链表首部添加元素
        首先将元素实例化为node
        """
        node = SingleNode(item)
        if self.is_empty():
            """如果链表为空"""
            self.__head = node
        else:               
            """链表非空"""
            cur = self.__head
            node.next = cur
            cur.prev = node
            self.__head = node
        
    def append(self,item):
        """尾部添加元素"""
        node = SingleNode(item)
        cur = self.__head
        if self.is_empty():
            cur = node
        else:
            while cur.next != None:
                cur = cur.next
            cur.next = node
            node.prev = cur
        
        
    def insert(self,pos,item):
        """在pos添加元素"""
        if pos <= 0:
            self.add(item)
        elif pos > self.length()-1:
            self.append(item)
        else:
            node = SingleNode(item)
            pre = self.__head
            count = 0
            while count < pos-1:
                """找到插入点的前一个节点,插入到该节点的后面"""
                pre = pre.next
                count += 1
            """此时pos-1处的节点为pre"""
            node.next = pre.next
            pre.next.prev = node
            node.prev = pre
            pre.next = node
                    
    def remove(self,item):
        """删除元素"""
        if self.is_empty():
            return
        cur =self.__head
        pre = None
        """如果是首节点"""
        if cur.item == item:
            cur.next.prev = None
            self.__head = cur.next
            return
        
        """如果是中间节点"""
        while cur.next != None:
            if cur.item == item:
                pre.next = cur.next
                cur.next.prev = pre
                return
            pre = cur
            cur = cur.next
        """如果是尾节点"""
        pre.next = None
            
    def research(self,item):
        """查找元素"""
        cur =self.__head
        while cur != None:
            if cur.item == item:
                return True
            cur = cur.next
        return False

if __name__ == "__main__":
    ll = DooubleLinkList()
    ll.add(1)
    ll.add(2)
    ll.append(3)
    ll.travel()
    ll.insert(2, 4)
    ll.insert(4, 5)
    ll.insert(0, 6)
    print("length:",ll.length())
    ll.travel()
    print( ll.research(3))
    print( ll.research(7))
    ll.remove(1)
    print( "length:",ll.length())
    ll.travel()
    ll.remove(6)
    ll.remove(5)
    print( "length:",ll.length())
    ll.travel()
    print( ll.research(3))
"""
2 1 3 
length: 6
6 2 1 4 3 5 
True
False
length: 5
6 2 4 3 5 
length: 3
2 4 3 
True
"""

3、栈

栈可以用顺序表实现,也可以用链表实现。遵循后进先出的原则

# -*- coding: utf-8 -*-
"""
Created on Thu Feb 25 10:37:44 2021

@author: daicong
"""

"""
栈的操作
Stack() 创建一个新的空栈
push(item) 添加一个新的元素item到栈顶
pop() 弹出栈顶元素
peek() 返回栈顶元素
is_empty() 判断栈是否为空
size() 返回栈的元素个数
"""

class Stack():
    """栈:后进先出LIFO"""
    def __init__(self):
        self.items=[]
    
    def push(self,item):
        """尾添加元素"""
        """只需要保持同一端进出就行,不用纠结是首端还是尾端,尾部插入时间复杂度低,这里items尾部表示栈顶"""
        return self.items.append(item)
    
    def pop(self):
        """弹出元素"""
        return self.items.pop()
    
    def peek(self):
        """返回栈顶元素"""
        """尾部表示栈顶,故栈顶元素为items的最后一个元素"""
        return self.items[-1]
    
    def is_empty(self):
        """判断栈是否为空"""
        return self.items == []
    
    def size(self):
        """返回栈的长度"""
        return len(self.items)
        
if __name__ == "__main__":
    stack = Stack()
    stack.push("hello")
    stack.push("world")
    stack.push("test")
    print(stack.size())
    print(stack.peek())
    print(stack.pop())
    print(stack.pop())
    print(stack.pop())

"""
3
test
test
world
hello
"""

4、队列

队列(Queue):是限定只能在表的一端进行插入和在另一端进行删除操作的线性表;
栈(Stack):是限定只能在表的一端进行插入和删除操作的线性表。
区别如下:
一、规则不同

  1. 队列:先进先出(First In First Out)FIFO
  2. 栈:先进后出(First In Last Out )FILO

二、对插入和删除操作的限定不同

  1. 队列:只能在表的一端进行插入,并在表的另一端进行删除;
  2. 栈:只能在表的一端插入和删除。

三、遍历数据速度不同

  1. 队列:基于地址指针进行遍历,而且可以从头部或者尾部进行遍历,但不能同时遍历,无需开辟空间,因为在遍历的过程中不影响数据结构,所以遍历速度要快;
  2. 栈:只能从顶部取数据,也就是说最先进入栈底的,需要遍历整个栈才能取出来,而且在遍历数据的同时需要为数据开辟临时空间,保持数据在遍历前的一致性。
# -*- coding: utf-8 -*-
"""
Created on Thu Feb 25 10:56:21 2021

@author: daicong
"""

"""
队列的操作
Queue() 创建一个空的队列
enqueue(item) 往队列中添加一个item元素
dequeue() 从队列头部删除一个元素
is_empty() 判断一个队列是否为空
size() 返回队列的大小
"""

class Queue():
    """队列:先进先出FIFO"""
    def __init__(self):
        self.items=[]
    
    def push(self,item):
        """尾添加元素"""
        return self.items.append(item)
    
    def pop(self):
        """弹出元素"""
        return self.items.pop()
    
    def peek(self):
        """返回队列顶元素"""
        return self.items[0]
    
    def is_empty(self):
        """判断队列是否为空"""
        return self.items == []
    
    def size(self):
        """返回队列的长度"""
        return len(self.items)
        
if __name__ == "__main__":
    queue = Queue()
    queue.push("hello")
    queue.push("world")
    queue.push("test")
    print(queue.items)
    print(queue.size())
    print(queue.peek())
    print(queue.pop())
    print(queue.pop())
    print(queue.pop())
    
"""
['hello', 'world', 'test']
3
hello
test
world
hello
"""    

5、双端队列

双端队列操作

Deque() 创建一个空的双端队列
add_front(item) 从队头加入一个item元素
add_rear(item) 从队尾加入一个item元素
remove_front() 从队头删除一个item元素
remove_rear() 从队尾删除一个item元素
is_empty() 判断双端队列是否为空
size() 返回队列的大小

# -*- coding: utf-8 -*-
"""
Created on Thu Feb 25 11:05:15 2021

@author: daicong
"""

"""
双端队列的操作
Deque() 创建一个空的双端队列
add_front(item) 从队头加入一个item元素
add_rear(item) 从队尾加入一个item元素
remove_front() 从队头删除一个item元素
remove_rear() 从队尾删除一个item元素
is_empty() 判断双端队列是否为空
size() 返回队列的大小
"""

class DoubleQueue():
    """双端队列:头部尾部都可以进出元素"""
    def __init__(self):
        self.items=[]
    
    def push_rear(self,item):
        """尾添加元素"""
        return self.items.append(item)
    
    def push_front(self,item):
        """首部加元素"""
        return self.items.insert(0,item)
    
    def pop_rear(self):
        """尾部弹出元素"""
        return self.items.pop()
    
    def pop_front(self):
        """首部弹出元素"""
        return self.items.pop(0)
    
    def peek_front(self):
        """返回队列顶元素"""
        return self.items[0]
    
    def peek_rear(self):
        """返回队列尾元素"""
        return self.items[-1]
    
    def is_empty(self):
        """判断队列是否为空"""
        return self.items == []
    
    def size(self):
        """返回队列的长度"""
        return len(self.items)
        
if __name__ == "__main__":
    doublequeue = DoubleQueue()
    doublequeue.push_rear("world")
    doublequeue.push_front("hello")
    doublequeue.push_rear("你好")
    doublequeue.push_rear("再见")
    print(doublequeue.items)
    print(doublequeue.size())
    print(doublequeue.peek_rear())
    print(doublequeue.peek_front())
    print(doublequeue.pop_front())
    print(doublequeue.pop_rear())

"""
['hello', 'world', '你好', '再见']
4
再见
hello
hello
再见
"""

6、排序

在这里插入图片描述

6.1冒泡排序

冒泡排序(英语:Bubble Sort)是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢”浮“到数列的顶端。

1.1、算法描述

  • 比较相邻的元素。如果第一个比第二个大,就交换他们两个;
  • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;​
  • 针对所有的元素重复以上的步骤,除了最后一个;
  • 重复以上步骤,直到排序完成

1.2、动图演示
在这里插入图片描述

1.3、代码实现

# -*- coding: utf-8 -*-
"""
Created on Thu Feb 25 11:51:10 2021

@author: daicong
"""
"""
排序思想:
一次过程:两两对比,找到最大值,放到最后面
n次过程:因为有n个元素,所以要放n次
"""
class Solution:
    def bubble_sort(self,nums):
        n =len(nums)
        for i in range(n):
            """外层循环表示已经排好了几次最大元素"""
            for j in range(n-i-1):
            	"""内层循环表示两两对比找最大"""
                if nums[j] > nums[j+1]:
                    nums[j],nums[j+1] = nums[j+1],nums[j]
        return nums

solution = Solution()
nums = [54,26,93,17,77,31,44,55,20]
res=solution.bubble_sort(nums)
print(res)

"""
[17, 20, 26, 31, 44, 54, 55, 77, 93]
"""

排序分析

最优时间复杂度:O(n) (表示遍历一次发现没有任何可以交换的元素,排序结束。)
最坏时间复杂度:O(n2)
稳定性:稳定

6.2选择排序

表现最稳定的排序算法之一,因为什么数据进去都是O(n^2)的时间复杂度,所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间,理论上讲,选择排序可能也是平时排序一般人想到最多的排序方法。

选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序列中找到最小或者最大的元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小或者最大元素,然后存放到已排序的序列的末尾。一次类推,直到所有元素均排序完毕。

2.1、算法描述

n个记录的直接选择排序可经过n-1趟直接选择排序得到有序的结果。具体算法描述如下:

  • 初始状态,无序区为R[1…n],有序区为空

  • 第一趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1…i-1]和R[i…n]。该趟排序从当前无序区中选出关键字最小的记录R[k],将它与无序区的第一个记录R交换,使得R[1…i]和R[i+1…n]分别变为记录个数增加1个的新有序区和记录个数减少一个的新无序区

  • n-1趟结束,数组有序化了。

2.2、动态演示
在这里插入图片描述

2.3、代码实现

# -*- coding: utf-8 -*-
"""
Created on Thu Feb 25 12:47:11 2021

@author: daicong
"""
"""
排序思想:
每次在未排序的元素中找出最大的值,插在前面已经排序的元素后面
"""

class Solution:
    def select_sort(self,nums):
        n = len(nums)
        for i in range(n):
            """外层循环表示已经排序的元素"""
            min_index=i
            for j in range(i,n):
                """内层循环表示从未排序的元素中找到最大值"""
                if nums[j] < nums[min_index]:
                    min_index = j
            nums[i],nums[min_index] = nums[min_index],nums[i]
        return nums

solution = Solution()
nums = [20, 54, 26, 31, 17, 44, 77, 93, 55]
res=solution.select_sort(nums)
print(res)               

"""
[17, 20, 26, 31, 44, 54, 55, 77, 93]
"""

2.4、算法分析
最佳情况:T(n)=O(n ^2)
最差情况:T(n)=O(n^2)
平均情况:T(n)=O(n^2)
稳定性:不稳定(考虑升序每次选择最大的情况)

6.3插入排序

插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额为空间排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

3.1、算法描述

一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:

  • 从第一个元素开始,该元素可以认为已经被排序;
  • 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  • 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  • 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  • 将新元素插入到该位置后;
  • 重复步骤2~5

3.2、动画演示
在这里插入图片描述
3.3、代码实现

# -*- coding: utf-8 -*-
"""
Created on Thu Feb 25 13:40:29 2021

@author: daicong
"""
class Solution:
    def insert_sort(self,nums):
        n = len(nums)
        for i in range(1,n):
            for j in range(i,0,-1):
                """
                与前一个元素比较,如果比前一个小,则交换位置
                需要注意的是,j的最小取值,应该是列表的第一的元素,故j-1最小为0,所以上面j的range最小是1(左闭右开取不到0)
                """
                if nums[j] < nums[j-1]:
                    nums[j-1],nums[j] = nums[j],nums[j-1]
        return nums

solution = Solution()
nums = [20, 54, 26, 31, 17, 44, 77, 93, 55]
res=solution.insert_sort(nums)
print(res)

"""
[17, 20, 26, 31, 44, 54, 55, 77, 93]
"""

3.4、算法分析

最佳情况:T(n)=O(n)
最坏情况T(n)=O(n^2)
平均情况T(n)=O(n^2)
稳定性:稳定

6.4希尔排序

希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序,同时该算法是冲破O(n^2)的第一批算法之一。它与插入排序的不同指出在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。

希尔排序是把记录按下表的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止

4.1、算法描述

我们来看下希尔排序的基本步骤,在此我们选择增量gap=length/2,缩小增量继续以gap = gap/2的方式,这种增量选择我们可以用一个序列来表示,{n/2,(n/2)/2…1},称为增量序列。希尔排序的增量序列的选择与证明是个数学难题,我们选择的这个增量序列是比较常用的,也是希尔建议的增量,称为希尔增量,但起始这个增量序列不是最优的。此处我们做示例使用希尔增量。

先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:

  • 选择一个增量序列t1, t2, …, tk,其中ti>tj,tk=1;
  • 按增量序列个数k,对序列进行k趟排序
  • 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m的子序列,分别对各字表进行直接插入排序。仅增量因子为1时,整个序列作为一个表来处理,表长度即为整个序列的长度

4.2、过程演示
在这里插入图片描述
4.3、代码实现

# -*- coding: utf-8 -*-
"""
Created on Thu Feb 25 14:30:40 2021

@author: daicong
"""

class Solution:
    def shell_sort(self,nums):
        n = len(nums)
        """首先第一次分组,组数为gap"""
        gap = n // 2
        
        while gap > 0:
            """对每一组的元素进行插入排序"""
            for i in range(gap, n):
                for j in range(i, gap-1, -gap):
                    """j最小为gap,nums[j-gap]=nums[0]刚好是第一个元素,解释同插入排序"""
                    if nums[j] < nums[j-gap]:
                        nums[j-gap],nums[j] = nums[j],nums[j-gap]
            gap = gap // 2
        return nums
    
solution = Solution()
nums = [20, 54, 26, 31, 17, 44, 77, 93, 55]
res=solution.shell_sort(nums)
print(res)

""""
[17, 20, 26, 31, 44, 54, 55, 77, 93]
"""

4.4、算法分析

最佳情况:T(n) = O(nlog2 n)
最坏情况:T(n) = O(nlog2 n)
平均情况:T(n) =O(nlog2n) 
稳定性:不稳定

6.5归并排序(Merge Sort)

和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(nlog n)的时间复杂度。代价是需要额外的内存空间。

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个非常典型的应用。归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

5.1、算法描述

  • 把长度为n的输入序列分成两个长度为n/2的子序列;
  • 对这两个子序列分别采用归并排序;
  • 将两个排序好的子序列合并成一个最终的排序序列

5.2、动图演示

在这里插入图片描述5.3、代码实现

# -*- coding: utf-8 -*-
"""
Created on Mon Mar  1 11:07:40 2021

@author: daicong
"""

class Solution:
    def mergeSort(self,nums):
        #分解
        n= len(nums)
        if n <= 1:
            """如果剩下一个数,那么就不分解了"""
            return nums
        mid = n//2
        left = self.mergeSort(nums[0:mid])
        right = self.mergeSort(nums[mid:])
        
        #合并
        result = []
        left_pointer,right_pointer = 0,0
        """左右列表个设置一个指针,分别对左右的列表中的元素进行对比排序,然后加入到result中"""
        while left_pointer < len(left) and right_pointer < len(right):
            if left[left_pointer] < right[right_pointer]:
                """如果小的话,就直接放到result中"""
                result.append(left[left_pointer])
                left_pointer +=1
            else:
                result.append(right[right_pointer])
                right_pointer +=1
        """将两个列表中剩余的没有append的进来的元素加进来"""
        """因为肯定有一个列表中指针先到底,跳出while循环,此时另一个列表的剩余元素就要单独加进来了"""
        #result.extend([left_pointer:])
        result += left[left_pointer:]
        result += right[right_pointer:]
        
        return result
            

solution = Solution()
nums = [54,26,93,17,77,31,44,55]
res=solution.mergeSort(nums)
print(res)

"""
[17, 26, 31, 44, 54, 55, 77, 93]
"""
"""
#拆分过程
[54,26,93,17,77,31,44,55]
[54,26,93,17] [77,31,44,55]
[54,26] [93,17] [77,31] [44,55]
[54] [26] [93] [17] [77] [31] [44] [55] 

#排序合并过程
[54] [26] [93] [17] [77] [31] [44] [55] 
[26,54] [17,93] [31,77] [44,55]
[17,26,54,93] [31,44,55,77]
[17, 26, 31, 44, 54, 55, 77, 93]
"""

5.4、算法分析

最佳情况:T(n) = O(n)
最差情况:T(n) = O(nlogn)
平均情况:T(n) = O(nlogn)

6.6快速排序(Quick Sort)

快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。

快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。

快速排序又是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。

快速排序的名字起的是简单粗暴,因为一听到这个名字你就知道它存在的意义,就是快,而且效率高!它是处理大数据最快的排序算法之一了。虽然 Worst Case 的时间复杂度达到了 O(n²),但是人家就是优秀,在大多数情况下都比平均时间复杂度为 O(n logn) 的排序算法表现要更好。

快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对两部分记录继续进行排序,以达到整个序列有序。

6.1、算法描述

快速排序使用分治法来把一个串(list)分为两个子串(sub-list)。具体算法描述如下:

  • 从数列中挑出一个元素,称为“基准”(pivot);
  • 重新排序数列,所有元素比基准值小的摆放在基准的前面,所有元素比基准值大的摆放在基准的后面(相同的数可以放到任意一边)。在这个区分退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
  • 递归(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序

6.2、动图演示
在这里插入图片描述
6.3、代码实现

def quick_sort(alist, start, end):
    """快速排序"""

    # 递归的退出条件
    if start >= end:
        return

    # 设定起始元素为要寻找位置的基准元素
    mid = alist[start]

    # low为序列左边的由左向右移动的游标
    low = start

    # high为序列右边的由右向左移动的游标
    high = end

    while low < high:
        # 如果low与high未重合,high指向的元素不比基准元素小,则high向左移动
        """这里只需要有一边有个等于号,就可以解决边界问题,比如[54,54,60,54,54] mid=54"""
        while low < high and alist[high] >= mid:
            high -= 1
        # 将high指向的元素放到low的位置上
        alist[low] = alist[high]

        # 如果low与high未重合,low指向的元素比基准元素小,则low向右移动
        while low < high and alist[low] < mid:
            low += 1
        # 将low指向的元素放到high的位置上
        alist[high] = alist[low]

    # 退出循环后,low与high重合,此时所指位置为基准元素的正确位置
    # 将基准元素放到该位置
    alist[low] = mid

    # 对基准元素左边的子序列进行快速排序
    quick_sort(alist, start, low-1)

    # 对基准元素右边的子序列进行快速排序
    quick_sort(alist, low+1, end)


alist = [54,26,93,17,77,31,44,55,20]
quick_sort(alist,0,len(alist)-1)
print(alist)

"""
[17, 20, 26, 31, 44, 54, 55, 77, 93]
"""
# -*- coding: utf-8 -*-
"""
Created on Thu Feb 25 15:20:15 2021

@author: daicong
"""
class Solution:
    def quickSort(self,arr, left=None, right=None):
        left = 0 if not isinstance(left,(int, float)) else left
        right = len(arr) - 1 if not isinstance(right,(int, float)) else right
        if left < right:
            partitionIndex = self.partition(arr, left, right)
            self.quickSort(arr, left, partitionIndex-1)
            self.quickSort(arr, partitionIndex+1, right)
        return arr
    
    def partition(self,arr, left, right):
        pivot = left
        index = pivot+1
        i = index
        while  i <= right:
            if arr[i] < arr[pivot]:
                self.swap(arr, i, index)
                index+=1
            i+=1
        self.swap(arr,pivot,index-1)
        return index-1
    
    def swap(self,arr, i, j):
        arr[i], arr[j] = arr[j], arr[i]

solution = Solution()
nums = [20, 54, 26, 31, 17, 44, 77, 93, 55]
res=solution.quickSort(nums)
print(res)

"""
[17, 20, 26, 31, 44, 54, 55, 77, 93]
"""

6.6、算法分析
快速排序的最坏运行情况是 O(n²),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn),且 O(nlogn) 记号中隐含的常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。

6.7堆排序

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:

  1. 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
  2. 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;

7.1、算法描述

  • 将待排序序列构建成一个堆 H[0……n-1],根据(升序降序需求)选择大顶堆或小顶堆;
  • 把堆首(最大值)和堆尾互换;
  • 把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;
  • 重复步骤 2,直到堆的尺寸为 1。

7.2、动图演示
在这里插入图片描述
7.3、代码实现

# -*- coding: utf-8 -*-
"""
Created on Thu Feb 25 15:27:46 2021

@author: daicong
"""
class Solution: 
    def buildMaxHeap(self,arr):
        import math
        for i in range(math.floor(len(arr)/2),-1,-1):
            self.heapify(arr,i)
    
    def heapify(self,arr, i):
        left = 2*i+1
        right = 2*i+2
        largest = i
        if left < arrLen and arr[left] > arr[largest]:
            largest = left
        if right < arrLen and arr[right] > arr[largest]:
            largest = right
    
        if largest != i:
            self.swap(arr, i, largest)
            self.heapify(arr, largest)
    
    def swap(self,arr, i, j):
        arr[i], arr[j] = arr[j], arr[i]
    
    def heapSort(self,arr):
        global arrLen
        arrLen = len(arr)
        self.buildMaxHeap(arr)
        for i in range(len(arr)-1,0,-1):
            self.swap(arr,0,i)
            arrLen -=1
            self.heapify(arr, 0)
        return arr
        
solution = Solution()
nums = [20, 54, 26, 31, 17, 44, 77, 93, 55]
res = solution.heapSort(nums)
print(res)

"""
[17, 20, 26, 31, 44, 54, 55, 77, 93]
"""

7.4、算法分析
堆排序的平均时间复杂度为 Ο(nlogn)

6.8计数排序

计数排序的核心在于将输入的数据值转化为键存储再额外的开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数

计数排序是一种稳定的排序算法。计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中的值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。它只能对整数进行排序。

8.1、算法描述

  • 找出待排序的数组中最大和最小的元素;
  • 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
  • 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
  • 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

8.2、动图演示
在这里插入图片描述
8.3、代码实现

# -*- coding: utf-8 -*-
"""
Created on Thu Feb 25 15:40:10 2021

@author: daicong
"""

class Solution:
    def countingSort(self, arr, maxValue):
        bucketLen = maxValue+1
        bucket = [0]*bucketLen
        sortedIndex =0
        arrLen = len(arr)
        for i in range(arrLen):
            if not bucket[arr[i]]:
                bucket[arr[i]]=0
            bucket[arr[i]]+=1
        for j in range(bucketLen):
            while bucket[j]>0:
                arr[sortedIndex] = j
                sortedIndex+=1
                bucket[j]-=1
        return arr
    
solution = Solution()
nums = [20, 54, 26, 31, 17, 44, 77, 93, 55]
maxValue = 100
res = solution.countingSort(nums,maxValue)
print(res)

"""
[17, 20, 26, 31, 44, 54, 55, 77, 93]
"""

8.4、算法分析
当输入的元素是n 个0到k之间的整数时,它的运行时间是 O(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。
最佳情况:T(n) = O(n+k)
最差情况:T(n) = O(n+k)
平均情况:T(n) = O(n+k)

6.9桶排序

桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。

桶排序的工作原理:假设输入数据服从均匀分布,将数据分有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)

9.1、算法描述

  • 人为设置一个BucketSize,作为每个桶所能放置多少个不同数值(例如当BucketSize==5时,该桶可以存放{1,2,3,4,5}这几种数字,但是容量不限,及可以存放100个3)

  • 遍历输入数据,并且把数据一个一个放到对应的桶里去;

  • 对每个不是空的桶进行排序,可以使用其它排序方法,也可以递归使用桶排序;

  • 从不是空的桶里把排好序的数据拼接起来

注意:如果递归使用桶排序为各个桶排序,则当桶的数量为1时要手动减小BucketSize增加下一循环桶的数量,否则会陷入死循环,导致内存溢出。

9.2、图片展示
9.3、代码实现
9.4、算法分析

桶排序最好情况下使用线性时间O(n),桶排序的时间复杂度,取决与对各个桶之间数据进行排序的时间复杂度,因为其它部分的时间复杂度都为O(n)。很显然,桶划分的越小,各个桶之间的数据越少,排序所用的时间也会越少。但相应的空间消耗就会增大。

最佳情况:T(n) = O(n+k)
最差情况:T(n) = O(n+k)
平均情况:T(n) = O(n2)

6.10基数排序

基数排序也是非比较排序算法,对每一位进行排序,从最低位开始排序,复杂度为O(kn),为数组长度,k为数组中得数得最大位数;

基数排序是按照低位优先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。

10.1、算法描述

  • 取得数组中的最大数,并取得位数;
  • arr为原始数组,从最低位开始取每个位组成radix数组;
  • 对radix进行计数排序(利用计数排序使用于小范围数的特点)

10.2、动图展示
在这里插入图片描述
10.3、代码实现

# -*- coding: utf-8 -*-
"""
Created on Thu Feb 25 15:44:27 2021

@author: daicong
"""

class Solution:
    def radix_sort(self,arr):
        digit = 0
        max_digit = 1
        max_value = max(arr)
        #找出列表中最大的位数
        while 10**max_digit < max_value:
            max_digit = max_digit + 1    
        while digit < max_digit:
            temp = [[] for i in range(10)]
            for i in arr:
                #求出每一个元素的个、十、百位的值
                t = int((i/10**digit)%10)
                temp[t].append(i)
            coll = []
            for bucket in temp:
                for i in bucket:
                    coll.append(i)            
            arr = coll
            digit = digit + 1
        return arr

solution = Solution()
nums = [20, 54, 26, 31, 17, 44, 77, 93, 55]
res = solution.radix_sort(nums)
print(res)

"""
[17, 20, 26, 31, 44, 54, 55, 77, 93]
"""

10.4、算法分析
最佳情况:T(n) = O(n * k)
最差情况:T(n) = O(n * k)
平均情况:T(n) = O(n * k)

基数排序有两种方法:

  • MSD 从高位开始进行排序
  • LSD 从低位开始进行排序

基数排序 vs 计数排序 vs 桶排序

这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:

  • 基数排序:根据键值的每位数字来分配桶
  • 计数排序:每个桶只存储单一键值
  • 桶排序:每个桶存储一定范围的数值

7、查找

搜索是在一个项目集合中找到一个特定项目的算法过程。搜索通常的答案是真的或假的,因为该项目是否存在。 搜索的几种常见方法:顺序查找、二分法查找、二叉树查找、哈希查找。

7.1二分查找

二分法查找概念
二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。因此,折半查找方法适用于不经常变动而查找频繁的有序列表。首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。
二分法查找适用于顺序表

# -*- coding: utf-8 -*-
"""
Created on Thu Feb 25 16:29:14 2021

@author: daicong
"""
"""二分非递归查找"""
class Solution:
    def binary_search(self,nums,target):
        first = 0
        last = len(nums)-1
        while first <= last:
            """
            推荐使用mid_index = first + (last - first)// 2
            与下面的语句等价,可避免内存溢出
            """
            mid_index = (first + last)// 2
            if nums[mid_index] == target:
                return True
            elif nums[mid_index] < target:
                """在右侧时"""
                first = mid_index + 1
            else:
                """在左侧时"""
                last = mid_index - 1
        return False

nums = [17, 44, 26, 31, 20, 54, 55, 77, 93]
target = 44
solution = Solution()
print("排序前:",solution.binary_search(nums, target))
nums.sort()
print("排序后:",solution.binary_search(nums, target))

"""
排序前: False
排序后: True
"""


"""二分递归查找"""
class Solution:
    def binary_search(self,nums,target):
        first = 0
        last = len(nums)-1
        while first <= last:
            """
            推荐使用mid_index = first + (last - first)// 2
            与下面的语句等价,可避免内存溢出
            """
            mid_index = (first + last)// 2
            if nums[mid_index] == target:
                return True
            elif nums[mid_index] < target:
                """在右侧时"""
                return self.binary_search(nums[mid_index+1:], target)
            else:
                """在左侧时"""
                return self.binary_search(nums[:mid_index-1], target)
        return False

nums = [17, 44, 26, 31, 20, 54, 55, 77, 93]
target = 44
solution = Solution()
print("排序前:",solution.binary_search(nums, target))
nums.sort()
print("排序后:",solution.binary_search(nums, target))

"""
排序前: False
排序后: True
"""

上面的mid_index = (first+last) // 2的计算方法可以进行优化,在数比较大的情况下会导致内存溢出
很容易证明(a+b) / 2 = a + (b-a) / 2

7.2二叉树查找

7.2.1构建二叉树

基本概念参考

深度优先搜索DFS(Depth First Search)
广度优先遍历BFS(Breadth First Search)
根结点D(Degree)、左子树L(Left)、右子树R(Right)
前序遍历DLR
中序遍历LDR
后序遍历LRD

# -*- coding: utf-8 -*-
"""
Created on Thu Feb 25 20:12:20 2021

@author: daicong
"""

class Node:
    """构建节点类"""
    def __init__(self,item):
        """二叉树的一个节点有三个参数元素、左子树、右子树"""
        self.item = item
        self.lchild = None
        self.rchild = None

class Tree:
    """构建树类"""
    def __init__(self,root=None):
        self.root = root
    
    def add(self,item):
        """添加节点"""
        node = Node(item)
        if self.root == None:
            """如果树为空,则添加的节点为根节点"""
            self.root = node
        else:
            queue = []
            """
            将树的每一个节点按照一层一层的放进去
            每次放进去的都是左子树或者右子树,一层一层的往下遍历
            """
            queue.append(self.root)
            while queue:
                cur = queue.pop(0)
                if cur.lchild == None:
                    """如果没有左子树,那么添加的节点为左子树"""
                    cur.lchild = node
                    return
                elif cur.rchild == None:
                    """如果没有右子树,那么添加的节点为右子树"""
                    cur.rchild = node
                    return
                else:
                    """如果左右子树都有,那么将左右子树放进队列中,一层一层的往下遍历,直到将节点加上去"""
                    queue.append(cur.lchild)
                    queue.append(cur.rchild)

    def preorder(self,root):
         """深度优先遍历:先序遍历 根->左子树->右子树"""
         if root == None:
             return
         print(root.item, end=" ")
         """每次先打印根节点,然后递归传递左节点和右节点进去,传入的左右节点在下一个递归中就变成了根,很容易理解"""
         self.preorder(root.lchild)
         self.preorder(root.rchild)
         
    def inorder(self,root):
         """深度优先遍历:中序遍历 左子树->根->右子树"""
         if root == None:
             return
         self.inorder(root.lchild)
         print(root.item, end=" ")
         self.inorder(root.rchild)
         
    def postorder(self,root):
         """深度优先遍历:后序遍历 左子树->右子树->根"""
         if root == None:
             return
         self.postorder(root.lchild)
         self.postorder(root.rchild)
         print(root.item, end=" ")
         
    def breadth_travel(self,root):
         """广度优先遍历"""
         if root == None:
             return 
         queue = []
         queue.append(root)
         while queue:
             cur = queue.pop(0)
             print(cur.item, end=" ")
             """一层一层的往下遍历,并将每一层的节点都放进队列中,每取出一个节点,在遍历前都先打印节点的元素item指"""
             if cur.lchild != None:
                 
                 """一层一层往下遍历,将节点放入队列中"""
                 queue.append(cur.lchild)
             if cur.rchild != None:
                 queue.append(cur.rchild)

if __name__ == "__main__":
    tree = Tree()
    for i in range(10):
        tree.add(i)
    print("\n广度优先搜索")
    tree.breadth_travel(tree.root)
    print("\n先序遍历")
    tree.preorder(tree.root)
    print("\n中序遍历")
    tree.inorder(tree.root)
    print("\n后序遍历")
    tree.postorder(tree.root)
    
"""
广度优先搜索
0 1 2 3 4 5 6 7 8 9 
先序遍历
0 1 3 7 8 4 9 2 5 6 
中序遍历
7 3 8 1 9 4 0 5 2 6 
后序遍历
7 8 3 9 4 1 5 6 2 0
"""         

7.3哈希查找

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值