2-3-数据结构-线性结构-顺序表 链表和哈希表

线性表

线性表是最基本的数据结构之一,在实际程序中应用非常广泛,它还经常被用作更复杂的数据结构的实现基础。

根据线性表的实际存储方式,分为两种实现模型:

  • 顺序表,
  • 链表,
    下面分别进行研究,

顺序表的研究

顺序表的基本形式,数据元素本身连续存储,

第一种情况:
如果顺序表的存储元素都是固定大小的,只需要一块空间,
第二种情况,
但是存储的每一个元素的大小不是全部固定的,比如列表中,可以是存储数字,但是也有字符串,等,
所以可以每一个值都是放到另外一个存储块中存储,然后把每一个存储块的地址按照顺序表存起来,
这种取值的时候,需要先找到地址,然后通过地址去找到存储块,找到具体的内容,

顺序表的实现,
比如存储8个元素,这个存储空间是一次性的向操作系统申请到的,
所以一个顺序表,除了要保存具体的存储内容以外,还需要一个表头,
表头需要两个信息一个是容量是多大也就是最大的存储信息,还有一个就是现在的存储内容,也就是现在存储了多少个元素了,
怎么把表头和信息存储到一起?
一种是存储在一起,这叫做一体式的存储,
一种是分开存储,这叫做分离式存储,在存储表头的时候加一个信息,就是存储内容的地址,也就是表头有三个了,容量,目前的存储数量,存储信息的地址,

一体式的存储和分离式存储,哪一个好?
读取:一体式的存储,直接跳过表头就可以读取了,分离式存储是多一步,先从表头获取到地址信息,然后到地址获取具体信息,
扩充数据:
一体式的存储容量是固定的,如果要多存储数据,必须要重新申请空白的空间,然后数据搬过来,表头也要搬过来,
分离式存储,表头不需要改,只需要地址指向新的地址就可以了,
所以为了考虑到数据的动态变化,还是更多的采用的是分离式的存储,

扩充的时候如何预估申请的空间呢?
比如原来的空间只能存储4个元素,为了存储第5个元素, 我需要重新申请空间,
第一种方式,固定申请,比如每次再申请10个空间,就是14个,这种节省空间,但是操作频繁,
第二种方式,加倍的方式,一开始4个,下一次就是翻倍,就是8个,这种空间牺牲一些, 但是时间会更有效率,这就是空间换取时间,

顺序表的弊端也很明显了,就是每次扩充数据,都需要重新申请空间,那么有没有一种结构,多一个数据,就往上加,而不是重新申请空间呢?
那就是链表了

链表的研究

一个列表,需要存储3个元素,200,300,400
不用顺序表,这种受限,所以采用每加一个元素就申请一个空间,然后在每一个元素的后面新开一个空间,用来指向另外一个元素,
这样层层链接,就可以了,这种就是链表,

链表如何实现:

链表中每一个元素都是一个对象,每一个对象都是一个节点,包含有数据域key和指向下一个节点的指针next。通过各个节点之间的互相连接,最终串联成一个列表
每一个元素保持两个内容,准确来讲不能叫做元素了,而是叫做节点,

  • 一个是信息,叫做数据区,
  • 一个是下个元素的链接地址,这叫做链接区,尾结点指向的区域就是指向空,

  • 每一个节点有两个属性,data和next,
  • data可以是各种数据
  • next是下一个节点,
  • 所以头指向的是第一个节点,尾部指向的事null,

  • 你看循环链表和单链表的唯一区别就是尾部指向的不是null,而是第一个节点,

  • 双向链表,每一个节点有三个部分,prev,data,next,prev指向的是前一个节点,

  • 明白了前面的循环链表,就明白了这个双向循环链表了,就是首尾都是指向的
  • 头指向尾,尾指向头,

双向链表和单链表的区别

  • 很明显单链表找一个节点的前面一个节点,是需要从头还是找的,而双向链表很容易就可以找到,
  • 但是双向链表很明显费空间,所以这是空间换时间的概念,

链表和列表有什么区别???

1,存储方式

列表是一种连续的存储结构,它在内存中分配一段连续的空间来存储元素,每个元素占用固定的空间。因此,列表的访问速度非常快,可以通过下标来直接访问任意位置的元素。

链表是一种离散的存储结构,它不需要一段连续的空间来存储元素,而是通过指针来连接一系列的节点。每个节点含数据和指向下节点的指针。因此,链表的访问速度比较慢,需要从头节点开始遍历,直到找到目标节点。

2,插入和删除,查询的差异

  • 列表是连续的,但是链表不是连续的,链表没有索引,列表有索引,
  • 因为列表是连续的存储结构,列表插入的时候后面的元素都要往后移动位置,所以这些操作的时间复杂度为O(n),但是链表插入是简单的,就是指向问题就好了,O(1)
  • 列表查询的时候可以使用索引,O(1),但是链表查询就要从头往后开始查了,O(n),

3,空间占用

列表在内存中分配一段连续的空间来存储元素,因此它的空间占用比较大。而链表不需要一段连续的空间来存储元素,它的空间占用比较小。

单向链表,

第一节点是头节点,第二个节点是尾结点,
Python中交换变量的操作,
a=10
b=10
a,b=b,a # 这就是交换,这只在Python中有,别的语言没有,
为什么能达到这个效果,本质是什么?
a是一个空间,10是一个空间,a的空间指向10的空间,
所以这种交换,就是指向的变化,所以不是真正的赋值
所以要记住,Python中的变量不是保存的内容,而是保持的地址,地址指向的地方保存的才是具体的信息,
所以a只是等于数字,字符串,列表等,这就是因为这个变量就是一个指向,这是Python和其他语言的差异,

python实现单向链表

class SingleNode(object):
    # 里面有两个元素
    def __init__(self,elem):
        self.elem=elem
        self.next=None  # 因为一开始只有一个元素,我不知道下一个元素是什么,
    

# 第一步:创建一个新的链表
# sll=SingleLinkList(),这里面是空的,没有任何的节点,head----指向None,
# 第二步:现在链表有了,要保存一个整数值100,就要构造一个节点,
# node=SingleNode(100)  # 这里面包含两个值,就是100,none,然后链表的head就不是none,就是第一个节点,
class SingleLinkList(object):
    """单链表"""
    def __init__(self,node=None):
        self.__head = None  # 私有属性    设置成空,就是说这个链表里面没有任何的节点,先

    def is_empty(self):
        """判断链表是否为空"""
        return self.__head == None  # 只要链表的head是none就是空的链表,

    # 如何实现这个长度,因为链表是  head----100,next---20,next---300,next---none
    # 所以遍历这个链表,如果next是none就到头了,
    def length(self):
        """链表长度"""
        # cur游标,用来移动遍历节点,
        cur = self.__head
        # count用来计数,
        count = 0
        # 尾节点指向None,当未到达尾部时
        while cur != None:
            count += 1
            cur = cur.next  # 将cur后移一个节点,这一句很关键,
        return count

    def travel(self):
        """遍历链表"""
        cur = self.__head
        while cur != None:
            print(cur.elem,end=" ")
            cur = cur.next

    # 在头部插入数据的逻辑
    # 第一步,把新元素的next指向第一个节点,
    # 第二步,把head指向这个新元素
    # 这个逻辑也是可以处理空链表的情况,
    def add(self, item):
        """头部添加元素"""
        # 先创建一个保存item值的节点
        node = SingleNode(item)
        # 将新节点的链接域next指向头节点,即_head指向的位置
        node.next = self.__head  # 这个__head是原来的头部节点,
        # 将链表的头_head指向新节点
        self.__head = node

    def append(self, item):
        """尾部添加元素"""
        node = SingleNode(item)
        # 先判断链表是否为空,若是空链表,则将_head指向新节点
        if self.is_empty():
            self.__head = node
        # 若不为空,则找到尾部,将尾节点的next指向新节点
        else:
            cur = self.__head
            while cur.next != None:
                cur = cur.next  # 这个是一个属性,保存了一个节点类对象了,通过一通遍历,就把cur变成了尾结点了,
            cur.next = node  # 这就是把尾结点的指针指向下一个节点,

    # 通过坐标插入的实现逻辑
    #   inset(2,300)
    # 第一步,让新的节点,指向原来节点是2的节点,
    # 第二步,让左边是1的节点,指向新的节点,
    # 因为传过来的坐标是用户使用的,所以需要控制,
    # 1,如果是传入的0
    # 2,如果是传入的比长度还要大
    def insert(self, pos, item):
        """指定位置添加元素"""
        # 若指定位置pos为第一个元素之前,则执行头部插入
        if pos <= 0:
            self.add(item)
        # 若指定位置超过链表尾部,则执行尾部插入
        elif pos > (self.length() - 1):
            self.append(item)
        # 找到指定位置
        else:
            node = SingleNode(item)
            count = 0
            # pre用来指向指定位置pos的前一个位置pos-1,初始从头节点开始移动到指定位置
            pre = self.__head
            while count < (pos - 1):
                count += 1
                pre = pre.next  # 如果pos是2,那这个pre.next就是原来坐标是2的节点,
            node.next = pre.next  # 新节点node的next指向插入位置的节点
            pre.next = node  # 将插入位置的前一个节点的next指向新节点

    # 传入一个数据,需要删除具体的数据,
    # 实现逻辑,
    # 需要把删除的节点的上一个节点的指针指向下一个节点,让这个链表连接起来,
    # 用两个游标来解决这个问题,
    # 1,cur游标指向head
    # 2,pre游标指向cur,
    # 3,cur指向cur.next
    def remove(self, item):
        """删除节点"""
        cur = self.__head
        pre = None
        while cur != None:
            if cur.elem == item:   # 找到了要删除的元素
                if cur == self.__head:  # 如果找到的要删除的节点正好是第一个节点,怎么判断是第一个节点?:cur == self.__head
                    self.__head = cur.next  # 直接是让head指向被删除节点的next
                else:  #
                    pre.next = cur.next  # 将删除位置前一个节点的next指向删除位置的后一个节点,这样的写法即使只有一个节点,也能实现,
                break
            else:
                # 继续按链表后移节点,这两句就是把两个游标都往后移动,
                pre = cur
                cur = cur.next

    # 实现逻辑
    # 还是需要遍历列表的,判断节点的数据和用户要找的数据,是否相等,如果相等返回true,否则就是false,
    def search(self, item):
        """链表查找节点是否存在,并返回True或者False"""
        cur = self.__head
        while cur != None:
            if cur.elem == item:
                return True
            cur = cur.next
        return False


if __name__ == "__main__":
    ll=SingleLinkList()
    print(ll.is_empty())
    print(ll.length())
    ll.append(1)
    print(ll.is_empty())
    print(ll.length())
    ll.append(2)
    ll.append(3)
    ll.append(4)
    ll.append(5)
    ll.add(9)
    ll.insert(-1,200)
    ll.insert(1,300)
    ll.insert(11,400)
    ll.remove(400)
    ll.travel()
 

实现单向链表,

逻辑:

  1. 因为链表的每一个节点,需要保存数据和链接,我们自己定义一个数据类型叫做节点类型来保存这两个信息,
  2. 定义一个单链表类,初始是没有节点的,head是空的
    3,实现几个方法
    判断是否为空,
    返回链表长度
    遍历
    头部添加,
    尾部添加
    中间添加
    删除节点,
    查找,

双向链表

每一个节点需要保存三个内容了,上一个节点的地址,本个节点的数据,下一个节点的地址,
这样就有一个后继节点和前驱节点,
对于头结点的前驱节点是空,对于尾节点的下一个节点是空,

python实现双向链表

class Node(object):
    """双向链表节点"""
    def __init__(self, item):
        self.item = item
        self.next = None
        self.prev = None


class DLinkList(object):
    """双向链表"""
    def __init__(self):
        self.__head = None

    def is_empty(self):  # 和单链表是一样的,
        """判断链表是否为空"""
        return self.__head is None  # self.__head == None你可以使用双等号,但是pep8推荐使用is,

    def length(self):  # 和单链表是一样的,
        """返回链表的长度"""
        cur = self.__head
        count = 0
        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

    # 这个和单链表不一样,
    # 你在头部新增了一个节点
    # 第一步,新的节点的next指向下一个节点
    # 第二步,新节点的等于head
    # 第三步,原来的头部节点的prev指向新的节点,
    def add(self, item):
        """头部插入元素"""

        node = Node(item)
        if self.is_empty():
            # 如果是空链表,将_head指向node
            self._head = node
        else:
            node.next = self.__head  # 空链表的时候self.__head还是一个none,所以next是等于none的,
            self.__head=node  # 让头部节点等于这个节点,
            node.next.prev = node  # node.next这是原来的头节点,他的prev指向新节点

    # 这个和单链表不一样,
    # 第一步,原来的尾结点的next指向新的节点,
    # 第二步,新的节点的prev,指向原来的尾结点,
    def append(self, item):
        """尾部插入元素"""
        node = Node(item)
        if self.is_empty():
            # 如果是空链表,将__head指向node
            self.__head = node
        else:
            # 移动到链表尾部
            cur = self.__head
            while cur.next != None:
                cur = cur.next  # 这一步就是移动到尾节点
            # 将尾节点cur的next指向node
            cur.next = node
            # 将node的prev指向cur
            node.prev = cur

    # 这个插入,和单链表不一样,
    # 第一步,新的节点的next指向原来位置的节点,node.next = cur
    # 第二步,新的节点的prev指向原来位置的上一个节点cur.prev,node.prev = cur.prev
    # 第三步,原来位置的上一个节点的next指向新的节点,node,prev.next = node
    # 第四步,原来位置的prev指向新的节点,cur.prev = node
    # 代码不是唯一的,但是要理清楚,这种可能是面试题,可以画图理解,
    def insert(self, pos, item):
        """在指定位置添加节点"""
        if pos <= 0:
            self.add(item)
        elif pos > (self.length() - 1):
            self.append(item)
        else:
            cur = self._head
            count = 0
            # 移动到指定位置的前一个位置
            while count < pos:
                count += 1
                cur = cur.next
            # 退出循环的时候,cur指向pos位置
            node = Node(item)
            node.next = cur   # 第一步,新的节点的next指向原来位置的节点
            node.prev = cur.prev      # 第二步,新的节点的prev指向原来位置的上一个节点
            node.prev.next = node      # 第三步,原来位置的上一个节点的next指向新的节点
            cur.prev = node    # 第四步,原来位置的prev指向新的节点

    # 和单链表的逻辑不一样,
    # 第一步:让删除节点的上一个节点的next,指向删除节点的下一个节点,cur.prev.next = cur.next
    # 第二步,让删除节点的下一个的prev,指向删除节点的上一个节点,cur.next.prev = cur.prev
    # 如果是首节点,
    # 第一步:让head等于当前节点的下一个节点
    # 第二步:把当前节点的下一个节点的prev变成none,
    # 只有一个节点的时候,
    # 直接让head等于当前节点的next,因为当前节点的next是none,
    def remove(self, item):
        """删除元素"""
        cur=self.__head
        while cur != None:
            if cur.item == item:
                if cur ==self.__head:
                    self.__head=cur.next
                    if cur.next: # 这是专门处理只有一个节点的时候,因为只有一个节点,cur.next.prev这是没有值的,
                        cur.next.prev=None
                else:
                    cur.prev.next = cur.next  # 将cur的前一个节点的next指向cur的后一个节点
                    if cur.next:
                        cur.next.prev = cur.prev  # 将cur的后一个节点的prev指向cur的前一个节点
                break
            else:
                cur=cur.next


    def search(self, item):
        """查找元素是否存在"""
        cur = self.__head
        while cur != None:
            if cur.item == item:
                return True
            cur = cur.next
        return False



if __name__ == "__main__":
    ll = DLinkList()
    ll.add(1)
    ll.add(2)
    ll.append(3)
    ll.insert(2, 4)
    ll.insert(4, 5)
    ll.insert(0, 6)
    ll.travel()
    ll.remove(1)
    ll.travel()
 

python实现单向循环链表

# 单向循环链表,
# 和单向链表的唯一区别在于尾结点的next指向了头结点了,


class Node(object):
    """节点"""
    def __init__(self, item):
        self.item = item
        self.next = None


class SinCycLinkedlist(object):
    """单向循环链表"""
    def __init__(self,node=None):
        self.__head = None
        if node:
            node.next=node  # 只有一个元素也要指向自己,

    def is_empty(self):  # 和单链表一样,
        """判断链表是否为空"""
        return self.__head == None

    def length(self):
        """返回链表的长度"""
        # 如果链表为空,返回长度0
        if self.is_empty():
            return 0
        count = 1
        cur = self.__head
        while cur.next != self.__head:  # 判断尾部的条件变了,如果一个节点的next是头部节点,这就是尾结点了,
            count += 1
            cur = cur.next
        return count

    def travel(self):
        """遍历链表"""
        if self.is_empty():
            return
        cur = self.__head
        while cur.next != self.__head:
            print(cur.item)
            cur = cur.next
        # 退出循环,cur指向尾结点,但是尾结点的元素未打印,
        print(cur.item)  # 对于空节点,一个节点的情况也是能满足的,

    # 逻辑
    # 第一步,把新节点的next指向原来的头节点
    # 第二步,把头结点指向到新节点,
    # 第三步,把尾结点的next指向头结点,
    # 如果是一个空的链表,
    def add(self, item):
        """头部添加节点"""
        node = Node(item)
        if self.is_empty():
            self.__head = node
            node.next = self.__head
        else:
            node.next = self.__head  # 添加的节点指向__head
            cur = self.__head  # 这是设置游标
            while cur.next != self.__head:
                cur = cur.next  # 不断的往后移动
            cur.next = node  # 退出循环,cur就是指向了尾结点,将尾部节点的next指向node
            self.__head = node  # 把头结点指向到新节点,

    # 逻辑
    # 第一步,把新的节点的next指向head,
    # 第二步,把原来的尾结点的next指向新节点,
    def append(self, item):
        """尾部添加节点"""
        node = Node(item)
        if self.is_empty():
            self.__head = node
            node.next = self.__head
        else:
            # 移到链表尾部
            cur = self.__head
            while cur.next != self.__head:
                cur = cur.next
            cur.next = node   # 将尾节点指向node
            node.next = self.__head  # 将node指向头节点__head

    # 逻辑和单链表是一样的,
    def insert(self, pos, item):
        """在指定位置添加节点"""
        if pos <= 0:
            self.add(item)
        elif pos > (self.length()-1):
            self.append(item)
        else:
            node = Node(item)
            cur = self.__head
            count = 0
            # 移动到指定位置的前一个位置
            while count < (pos-1):
                count += 1
                cur = cur.next
            node.next = cur.next
            cur.next = node

    # 逻辑
    # 如果删除的是头结点,
    # 第一步,head指向删除节点的下一个节点
    # 第二步,尾结点的next指向新的head,
    # 如果不是头结点,
    # 和单链表一样,直接把删除节点上一个节点的next指向,删除节点的下一个节点,
    def remove(self, item):
        """删除一个节点"""
        # 若链表为空,则直接返回
        if self.is_empty():
            return

        cur = self.__head
        pre = None
        while cur.next != self.__head:
            if cur.item==item:
                if cur.next == self.__head:  # 这是头结点的情况,
                    #如果是头结点还是需要遍历一遍,
                    rcur=self.__head
                    while rcur.next != self.__head:
                        rcur=rcur.next
                    self.__head = cur.next
                    rcur.next=self.__head
                else:  # 不是头结点的情况
                    pre.next=cur.next
                return
            else:
                pre= cur
                cur=cur.next
        # 退出循环,cur指向尾结点,
        if cur.item == item:
            if cur ==self.__head:
                self.__head=None
            else:
                pre.next = cur.next

    # 和单链表不一样
    def search(self, item):
        """查找节点是否存在"""
        if self.is_empty():
            return False
        cur = self.__head
        while cur.next != self.__head:  # 这种遍历把尾部节点漏掉了,对尾结点单独处理一下,
            if cur.item == item:
                return True
            else:
                cur = cur.next
        # 退出循环,cur指向的尾结点,
        if cur.item == item:  # 对尾节点进行判断,
            return True
        return False


# 所以你会发现整个过程,代码不是问题,都是最基本的,关键是要把逻辑理清楚,
# 你还可以对双向链表进行扩充,就是变成双向循环链表,

if __name__ == "__main__":
    ll = SinCycLinkedlist()
    ll.add(1)
    ll.add(2)
    ll.append(3)
    ll.insert(2, 4)
    ll.insert(4, 5)
    ll.insert(0, 6)
    ll.travel()
    ll.remove(1)
    ll.travel()
 

为什么要用python实现链表?

最近在学“python及其数据结构”,学完用python实现链表后感觉完全没有实用性啊,实属困惑,链表的功能用python自己的list不是都能实现吗?
网上的回答:学习链表之类的数据结构的关键不在于用什么语言实现,而是让你们理解,你所谓那些list等已经有的轮子底层是怎么实现的,

哈希表

哈希表(又称为散列表),是一种线性表的存储结构。哈希表由一个顺序表(数组)和一个哈希函数组成。哈希函数h(k)将k作为自变量,返回元素的存储下标。

哈希表在Python中的应用
a、字典与集合都是通过哈希表来实现的
b、 在Python中的字典:
a = {'name': 'Alex', 'age': 18, 'gender': 'Man'},使用哈希表存储字典,通过哈希函数将字典的键映射为下标。
c、在字典键值对数量不多的情况下,几乎不会发生哈希冲突,此时查找一个元素的时间复杂度为O(1)。

posted @ 2020-02-23 01:42  技术改变命运Andy  阅读(441)  评论(0编辑  收藏  举报