数据类型与结构(python)

本文介绍了Python中的基本数据类型和数据结构,包括列表、元组、数组、集合和字典。在数据结构部分,详细讨论了栈、队列、树、堆和链表的原理和实现,以及哈希表的概念。文章还探讨了栈和队列的经典问题,如它们的内存管理和在程序内存布局及数据结构中的不同作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、基本数据类型

     list列表;tuple元组;array数组;set集合,字典(dict),在它们的基础上可以方便快捷的实现常用的数据结构:栈,队列,链表,树等,而没有必要重复造轮子。

   

二、数据结构

2.1 树(tree)

 

2.2 栈(stack)

       栈是一种运算受限的线性表,其限制是指只仅允许在表的一端进行插入和删除操作,这一端被称为栈顶(Top),相对地,把另一端称为栈底(Bottom)。把新元素放到栈顶元素的上面,使之成为新的栈顶元素称作进栈、入栈或压栈(Push);把栈顶元素删除,使其相邻的元素成为新的栈顶元素称作出栈或退栈(Pop)。这种受限的运算使栈拥有“先进后出”的特性(First In Last Out),简称FILO。

       栈分顺序栈和链式栈两种。栈是一种线性结构,所以可以使用数组或链表(单向链表、双向链表或循环链表)作为底层数据结构。使用数组实现的栈叫做顺序栈,使用链表实现的栈叫做链式栈,二者的区别是顺序栈中的元素地址连续,链式栈中的元素地址不连续。

      栈的结构如下图所示:

栈的基本操作包括初始化、判断栈是否为空、入栈、出栈以及获取栈顶元素等。

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

    def push(self, value):    # 进栈
        self.stack.append(value)

    def pop(self):  #出栈
        if self.stack:
            self.stack.pop()
        else:
            raise LookupError('stack is empty!')

    def is_empty(self): # 如果栈为空
        return bool(self.stack)

    def top(self): 
        #取出目前stack中最新的元素
        return self.stack[-1]

2.3 队列(queue)

        队列(queue)是一种具有先进先出特征的线性数据结构,元素的增加只能在一端进行,元素的删除只能在另一端进行。能够增加元素的队列一端称为队尾,可以删除元素的队列一端则称为队首。

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

    def push(self, value):    # 进列
        self.queue.append(value)

    def pop(self):  #出列
        if self.queue:
            self.queue.pop(0)
        else:
            raise LookupError('queue is empty!')

    def is_empty(self): # 如果列为空
        return bool(self.queue)

    def top(self): 
        #取出目前queue中最新的元素
        return self.stack[-1]

 

双向队列模块deque

>>> from collections import deque
>>> queue = deque(["Eric", "John", "Michael"])
>>> queue.append("Terry")           # Terry arrives
>>> queue.append("Graham")          # Graham arrives
>>> queue.popleft()                 # The first to arrive now leaves
'Eric'
>>> queue.popleft()                 # The second to arrive now leaves
'John'
>>> queue                           # Remaining queue in order of arrival
deque(['Michael', 'Terry', 'Graham'])

2.4 堆(heap)

2.5 链表(linked list)

         链表是一种物理存储单元上非连续、非顺序的存储结构数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。

        头结点的数据域可以不存储任何信息,头结点的指针域存储指向第一个结点的指针(即第一个元素结点的存储位置)。头结点的作用是使所有链表(包括空表)的头指针非空,并使对单链表的插入、删除操作不需要区分是否为空表或是否在第一个位置进行,从而与其他位置的插入、删除操作一致。

详细阐述见此:https://blog.csdn.net/bufengzj/article/details/90694035

单链表的类的python实现:

#coding:utf-8
#先定一个node的类
class Node():                  #value + next
    def __init__ (self, value = None, next = None):
        self._value = value
        self._next = next
 
    def getValue(self):
        return self._value
 
    def getNext(self):
        return self._next
 
    def setValue(self,new_value):
        self._value = new_value
 
    def setNext(self,new_next):
        self._next = new_next
 
#实现Linked List及其各类操作方法
class LinkedList():
    def __init__(self):      #初始化链表为空表
        self._head = Node()
        self._tail = None
        self._length = 0
 
    #检测是否为空
    def isEmpty(self):
        return self._head == None
 
    #add在链表前端添加元素:O(1)
    def add(self,value):
        newnode = Node(value,None)    #create一个node(为了插进一个链表)
        newnode.setNext(self._head)
        self._head = newnode
        self._length=self._length+1
 
    #append在链表尾部添加元素:O(n)
    def append(self,value):
        newnode = Node(value)
        self._length = self._length + 1
        if self.isEmpty():
            self._head = newnode   #若为空表,将添加的元素设为第一个元素
        else:
            current = self._head
            while current.getNext() != None:
                current = current.getNext()   #遍历链表
            current.setNext(newnode)   #此时current为链表最后的元素
 
    #search检索元素是否在链表中
    def search(self,value):
        current=self._head
        foundvalue = False
        while current != None and not foundvalue:
            if current.getValue() == value:
                foundvalue = True
            else:
                current=current.getNext()
        return foundvalue
 
    #index索引元素在链表中的位置
    def index(self,value):
        current = self._head
        count = 0
        found = None
        while current != None and not found:
            count += 1
            if current.getValue()==value:
                found = True
            else:
                current=current.getNext()
        if found:
            return count
        else:
            raise ValueError ('%s is not in linkedlist'%value)
 
    #remove删除链表中的某项元素
    def remove(self,value):
        current = self._head
        self._length = self._length - 1
        pre = None
        while current!=None:
            if current.getValue() == value:
                if not pre:
                    self._head = current.getNext()
                else:
                    pre.setNext(current.getNext())
                break
            else:
                pre = current
                current = current.getNext()
 
    #insert链表中插入元素
    def insert(self,pos,value):
        self._length = self._length + 1
        if pos <= 1:
            self.add(value)
        elif pos > self.size():
            self.append(value)
        else:
            temp = Node(value)
            count = 1
            pre = None
            current = self._head
            while count < pos:
                count += 1
                pre = current
                current = current.getNext()
            pre.setNext(temp)
            temp.setNext(current)
#测试:
A=LinkedList()
A.add(4)
A.add(3)
A.append(5)
A.remove(5)
print(A._length)
print(A._head.getValue())

2.6 图(map)

2.7 哈希表(hash)

         散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表

      数组的特点是:寻址容易,插入和删除困难;

     而链表的特点是:寻址困难,插入和删除容易。

      那么我们能不能综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?答案是肯定的,这就是我们要提起的哈希表,哈希表有多种不同的实现方法,我接下来解释的是最常用的一种方法——拉链法,我们可以理解为“链表的数组”,如图:
 

        左边很明显是个数组,数组的每个成员包括一个指针,指向一个链表的头,当然这个链表可能为空,也可能元素很多。我们根据元素的一些特征把元素分配到不同的链表中去,也是根据这些特征,找到正确的链表,再从链表中找出这个元素。

#转自:https://www.cnblogs.com/hanahimi/p/4765265.html
class LinearMap(object):
    """ 线性表结构 """
    def __init__(self):
          self.items = []

    def add(self, k, v):    # 往表中添加元素
          self.items.append((k,v))

    def get(self, k):       # 线性方式查找元素
          for key, val in self.items:
             if key==k:      # 键存在,返回值,否则抛出异常
                 return val
          raise KeyError


class BetterMap(object):
    """ 利用LinearMap对象作为子表,建立更快的查询表 """

    def __init__(self, n=100):
          self.maps = []  # 总表格
          for i in range(n):  # 根据n的大小建立n个空的子表
            self.maps.append(LinearMap())

    def find_map(self, k):  # 通过hash函数计算索引值
          index = hash(k) % len(self.maps)   #相同对象含有相同哈希值,但是反过来不一定
          return self.maps[index]  # 返回索引子表的引用

    # 寻找合适的子表(linearMap对象),进行添加和查找
    def add(self, k, v):
          m = self.find_map(k)
          m.add(k, v)

    def get(self, k):
          m = self.find_map(k)
          return m.get(k)


if __name__ == "__main__":
    table = BetterMap()
    pricedata = [("Hohner257", 257),
                 ("SW1664", 280),
                 ("SCX64", 1090),
                 ("SCX48", 830),
                 ("Super64", 2238),
                 ("CX12", 1130),
                 ("Hohner270", 620),
                 ("F64C", 9720),
                 ("S48", 1988)]

    for item, price in pricedata:
          table.add(k=item, v=price)

    print(table.get("CX12"))
    # >>> 1130
    print(table.get("QIMEI1248"))
    # >>> raise KeyError

三、经典问题

3.1 堆与栈的区别

        使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就 
  走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自 
  由度小。   
        使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由 
  度大。

    参考:https://blog.csdn.net/K346K346/article/details/80849966

     堆(Heap)与栈(Stack)是开发人员必须面对的两个概念,在理解这两个概念时,需要放到具体的场景下,因为不同场景下,堆与栈代表不同的含义。一般情况下,有两层含义:
(1)程序内存布局场景下,堆与栈表示的是两种内存管理方式;
(2)数据结构场景下,堆与栈表示两种常用的数据结构。

在程序内存里:

堆与栈实际上是操作系统对进程占用的内存空间的两种管理方式,主要有如下几种区别:
(1)管理方式不同。栈由操作系统自动分配释放,无需我们手动控制;堆的申请和释放工作由程序员控制,容易产生内存泄漏;

(2)空间大小不同。每个进程拥有的栈的大小要远远小于堆的大小。理论上,程序员可申请的堆大小为虚拟内存的大小,进程栈的大小64bits的Windows默认1MB,64bits的Linux默认10MB;

(3)生长方向不同。堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低。

(4)分配方式不同。堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是由操作系统完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由操作系统进行释放,无需我们手工实现。

(5)分配效率不同。栈由操作系统自动分配,会在硬件层级对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是由C/C++提供的库函数或运算符来完成申请与管理,实现机制较为复杂,频繁的内存申请容易产生内存碎片。显然,堆的效率比栈要低得多。

(6)存放内容不同。栈存放的内容,函数返回地址、相关参数、局部变量和寄存器内容等。当主函数调用另外一个函数的时候,要对当前函数执行断点进行保存,需要使用栈来实现,首先入栈的是主函数下一条语句的地址,即扩展指针寄存器的内容(EIP),然后是当前栈帧的底部地址,即扩展基址指针寄存器内容(EBP),再然后是被调函数的实参等,一般情况下是按照从右向左的顺序入栈,之后是被调函数的局部变量,注意静态变量是存放在数据段或者BSS段,是不入栈的。出栈的顺序正好相反,最终栈顶指向主函数下一条语句的地址,主程序又从该地址开始执行。堆,一般情况堆顶使用一个字节的空间来存放堆的大小,而堆中具体存放内容是由程序员来填充的。

从以上可以看到,堆和栈相比,由于大量malloc()/free()或new/delete的使用,容易造成大量的内存碎片,并且可能引发用户态和核心态的切换,效率较低。栈相比于堆,在程序中应用较为广泛,最常见的是函数的调用过程由栈来实现,函数返回地址、EBP、实参和局部变量都采用栈的方式存放。虽然栈有众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,主要还是用堆。

无论是堆还是栈,在内存使用时都要防止非法越界,越界导致的非法内存访问可能会摧毁程序的堆、栈数据,轻则导致程序运行处于不确定状态,获取不到预期结果,重则导致程序异常崩溃,这些都是我们编程时与内存打交道时应该注意的问题。
比如,经典例子:

main.cpp 
int a = 0; 全局初始化区 
char *p1; 全局未初始化区 
main() 
{ 
int b; 栈 
char s[] = "abc"; 栈 
char *p2; 栈 
char *p3 = "123456"; 123456\0在常量区,p3在栈上。 
static int c =0; 全局(静态)初始化区 
p1 = (char *)malloc(10); 堆 
p2 = (char *)malloc(20); 堆 
}

 

分类:

线性表

复杂度(搜索,

    随机存取(有时亦称直接访问)代表同一时间访问一组序列中的一个随意组件。反之则称循序访问,即是需要更多时间去访问一个远程组件。

3.3 存储密度

参考文献:

1.《堆与栈的区别》,https://blog.csdn.net/K346K346/article/details/80849966

2. 《栈和队列在python中的实现》,https://www.cnblogs.com/yiduobaozhiblog1/p/9272556.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值