数据结构相关知识

一、什么是数据结构?

数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组成。
简单来说,数据结构就是设计数据以何种方式组织并存储在计算机中。
比如:列表、集合与字典等都是一种数据结构。

“程序=数据结构+算法”

二、数据结构的分类

数据结构按照其逻辑结构分为线性结构、数结构、图结构

  • 线性结构:数据结构中的元素存在一对一的相互关系
  • 树结构:数据结构中的元素窜在一对多的相互关系
  • 图结构:数据结构中的元素存在多对多的相互关系

三、线性结构

1.栈

1、定义:栈是一个数据集合,可以理解为只能在一端进行插入或者删除操作的列表。

2、栈的特点:后进先出(last-in,first-out),简称LTFO表

3、栈的概念:

  • 栈顶:允许插入和删除的这一端称之为栈顶
  • 栈底:另一固定的一端称为栈底
  • 空栈:不含任何元素的栈称为空栈

4、栈的基本操作:

  • 进栈(压栈):push
  • 出栈:pop
  • 取栈顶:gettop

如图:

  1. 栈的python实现

不需要自己定义,使用列表结构即可

  • Stack() 创建一个空的新栈。 它不需要参数,并返回一个空栈。
  • push(item)将一个新项添加到栈的顶部。它需要 item 做参数并不返回任何内容。
  • pop() 从栈中删除顶部项。它不需要参数并返回 item 。栈被修改。
  • peek() 从栈返回顶部项,但不会删除它。不需要参数。 不修改栈。
  • isEmpty() 测试栈是否为空。不需要参数,并返回布尔值。
  • size() 返回栈中的 item 数量。不需要参数,并返回一个整数。
class Stack():
    #构造一个空战
    def __init__(self):
        self.items=[]
    def push(self,item):
        self.items.append(item)
    def pop(self):
        return self.items.pop()
    def peek(self):
        return len(self.items)-1
    def isEmpty(self):
        return self.items ==[]
    def size(self):
        return len(self.items)

s = Stack()
print(s.isEmpty())
s.push(1)
s.push(2)
s.push(3)
print(s.pop())
print(s.pop())
print(s.pop())

# 输出结果:
True
3
2
1

2.队列

  1. 介绍

    • 队列是一个数据集合,仅允许在列表的一端进行插入,另一端进行删除,
    • 进行插入的一端称为队尾(rear),插入动作称之为进队或入队
    • 进行删除的一端称之为对头(front),删除动作称为出队

    双向队列:对列的两端都允许进行进队和出队操作

  2. 队列的实现

    队列能否简单用列表实现?为什么

    • 初步设想:列表+两个下标指针
    • 创建一个列表和两个变量,front变量指向队首,rear变量指向队尾。初始时,front和rear都为0。
    • 进队操作:元素写到li[rear]的位置,rear自增1。
    • 出队操作:返回li[front]的元素,front自减1。

  3. 示例
    • Queue() 创建一个空的新队列。 它不需要参数,并返回一个空队列。
    • enqueue(item) 将新项添加到队尾。 它需要 item 作为参数,并不返回任何内容。
    • dequeue() 从队首移除项。它不需要参数并返回 item。 队列被修改。
    • isEmpty() 查看队列是否为空。它不需要参数,并返回布尔值。
    • size() 返回队列中的项数。它不需要参数,并返回一个整数。
    class Queue():
        def  __init__(self):
            self.items = []
        def enqueue(self,item):
            self.items.insert(0,item)
        def dequeue(self):
            return self.items.pop()
        def isEmpty(self):
            return self.items == []
        def size(self):
            return len(self.items)
    
    q = Queue()
    q.enqueue(1)
    q.enqueue(2)
    q.enqueue(3)
    print(q.dequeue())
    print(q.dequeue())
    print(q.dequeue())
    # 结果为 123
  • 案例:烫手的山芋

    • 烫手山芋游戏介绍:6个孩子围城一个圈,排列顺序孩子们自己指定。第一个孩子手里有一个烫手的山芋,需要在计时器计时1秒后将山芋传递给下一个孩子,依次类推。规则是,在计时器每计时7秒时,手里有山芋的孩子退出游戏。该游戏直到剩下一个孩子时结束,最后剩下的孩子获胜。请使用队列实现该游戏策略,排在第几个位置最终会获胜。
    • 提取有价值的信息:
      • 计时1s的时候删除在第一个孩子手里面
      • 山芋会被1s传递一次
      • 7秒钟山芋被传递了6次
      • 准则:保证第一个(队头)孩子手里面永远要有山芋
    kids = ['A','B','C','D','E','F']
    q = Queue()
    for kid in kids:
        q.enqueue(kid)
    while q.size() > 1:#当队列中孩子的个数大于1游戏继续否则游戏结束
        for i in range(1,7):#山芋传递的次数
            #对头元素出队列在入队列
            kid = q.dequeue()
            q.enqueue(kid)
        q.dequeue()#一轮游戏结束后,将对头孩子淘汰(对头孩子手里永远有山芋)
    

3.双端队列 待完成

  1. 介绍

    • 同同列相比,有两个头部和尾部。可以在双端进行数据的插入和删除,提供了单数据结构中栈和队列的特性
  2. 实例
    • Deque() 创建一个空的新 deque。它不需要参数,并返回空的 deque。
    • addFront(item) 将一个新项添加到 deque 的首部。它需要 item 参数 并不返回任何内容。
    • addRear(item) 将一个新项添加到 deque 的尾部。它需要 item 参数并不返回任何内容。
    • removeFront() 从 deque 中删除首项。它不需要参数并返回 item。deque 被修改。
    • removeRear() 从 deque 中删除尾项。它不需要参数并返回 item。deque 被修改。
    • isEmpty() 测试 deque 是否为空。它不需要参数,并返回布尔值。
    • size() 返回 deque 中的项数。它不需要参数,并返回一个整数。

    双端队列应用案例:回文检查 待补充

    • 回文是一个字符串,读取首尾相同的字符,例如,radar toot madam。
    class Queue():
        def  __init__(self):
            self.items = []
        def addFront(self,item):
            self.items.insert(0,item)
        def addRear(self):
            self.items.append(self)
        def removeFront(self):
            self.items.pop(0)
        def size(self):
            return len(self.items)

4. 顺序表

  1. 概念

    • 集合中存储的元素是有顺序的,顺序表的结构可以分为两种形式:单数据类型和多数据类型。
    • python中的列表和元组就属于多数据类型的顺序表
    • 列表,元组,字典等属于python中的高级数据结构,现在我们研究的是存储的本质,所以不用这些高级结构;
      这里我们要使用的是内存存储,计算机中的内存时用来存储数据以及和cpu打交道的;
  2. 位,比特bit,字节Byte,字之间的关系
    1位=1bit比特;
    1Byte字节=8位;
    1字=2Byte字节;
    1字=16位。
    
    1 bit = 1  二进制数据
    1 byte  = 8  bit
    1 字母 = 1  byte = 8 bit
    1 汉字 = 2  byte = 16 bit
    
    一个二进制数据0或1,是1bit;
    
    存储空间的基本计量单位,如:MySQL中定义 VARCHAR(45) 即是指 45个字节;
    1 byte = 8 bit
    
     字母 = 1 byte = 8 bit
  3. 顺序表单数据类型的基本格式

    内存的基本单位是1Byte;
    内存是一个连续的存储空间;

    int a = 1 

    在内存中存储时,占了四个存储单元,即占了4*8bit;
    一个字符即一个char占一个字节存储单元;
    基础数据类型不同,所占用的存储单元也不同;
    在存储的时候,如果存储的是数字,那么在取连续的四个存储单元是会当做一个整体取出然后转换为一个数字;如果存储的是一个字符,那么取的时候会当做四个字符取出;
    存储数字1的时候,会在内存中存储 00000000 00000000 00000000 00000001;

    0x01 00000000
    0x02 00000000
    0x03 00000000
    0x04 00000001

    在存储一组数据 1,2,3 时,我们要将这三个数字在内存中连续的存储,以方便取出;
    那么就是

    0x01 1
    0x05 2
    0x09 3

    这样存储的;
    我们可以这样理解,当我们存储一个纯数字列表的时候,存储了列表第一个元素在内存中的初始位置,当我们要按照下标去取数据的时候,因为数据在内存中是连续存储的,假设存储的第一个数据是0x01,那么第二个数据就是 0x0(1+4),第三个数据是 0x0(1+4*2),即按照数据的下标进行数据偏移即可;
    所以当一组数据全部是相同数据类型时,我们按照一组数据在内存中紧靠在一起存放,那么这种数据存储形式就叫做顺序表;

    编程语言中的索引从0开始的原因是存储数据时,变量指向的是第一个元素的内存位置,它的偏移量为0,即索引的0代表的就是偏移量为0;

  4. 顺序表多数据类型

  5. 总结
    • 单数据类型顺序表的内存图(内存连续开启)
    • 多数据类型顺序表的内存图(内存非连续开辟)
    • 顺序表的弊端:顺序表的结构需要预先知道数据大小来申请连续的存储空间,而在进行扩充时又需要进行数据的搬迁

5.链表 待完成

链表中每一个元素都是一个对象,每一个对象都是一个节点,包含有数据域key和指向下一个节点的指针next。通过各个节点之间的互相连接,最终串联成一个列表

is_empty():链表是否为空

. length():链表长度

. travel():遍历整个链表

. add(item):链表头部添加元素

. append(item):链表尾部添加元素

. insert(pos, item):指定位置添加元素

. remove(item):删除节点

. search(item):查找节点是否存在

  1. 节点定义:

    class Node():
        def __init__(self,item)
        self.item = item # 为了存储数值数据
        self.next = None # 为了存储下一个节点的地址
  2. 建立链表
    class Link():
        #构建一个空链表
        def __init__(self):
            #_head要指向第一个节点,,如果没有节点则指向None
            self._head = None
         def add(self,item):
            #1.创建一个新节点
            node = Node(item)
            node.next = self._head
            self._head = node
         def is_empty(self):
            return self._head == None
         def length(self):
            cur = self._head
            count = 0#记录节点的个数
            while cur:
                count += 1
                cur = cur.next
            return count
    
  3. 链表的遍历

    # 继创建链表类中继续写方法
    def travel(self):
        #在非空的链表中head永远要指向第一个节点的地址 ,永远不要修改它的指向,否则会造成数据的丢失
        cur = self._head
        while cur:
            print(cur.item)
            cur = cur.next
  4. 链表的添加
    # 继创建链表类中继续写方法
    def append(self,item):
            node = Node(item)
            cur = self._head #当前节点
            pre = None #指向cur的前一个节点
            if self._head == None:#如果链表为空则需要单独处理
                self._head = node
                return
    
            while cur:
                pre = cur
                cur = cur.next#循环结束之后cur指向了None,pre指向了最后一个节点
    
            pre.next = node
  5. 链表的插入和删除
    • 插入

      # 继创建链表类中继续写方法
      def insert(self,pos,item)
         node = Node(item)
          cur = self._head#当前节点
          pre = None
          #如果插入的位置大于了链表的长度,则默认插入到尾部
          if post > self.length()-1:
              self.append(item)
              return
          #否则 查找要插入的节点
          for i in range(pos):
              pre = cur# 指向cur的前一个节点
              cur = cur.next#
          pre.next = node
          node.next = cur
    • 删除

      # 继创建链表类中继续写方法
      def remove(self,item):
              cur = self._head#当前节点
              pre = None# cur的前一个节点
      
              if item == cur.item:# 如果删除的元素和第一个节点相同
                  self._head = cur.next# 则head指向下一个节点
                  return
      
              while cur:
                  if cur.item != item:# 如果当前节点 不等于要删除的节点
                      pre = cur# cur的前一个节点
                      cur = cur.next# 下一个节点
                  else:
                      break
              # cur前一个节点next 值为 cur下一个节点
              pre.next = cur.next
  6. 完整版
    class Node():
        def __init__(self,item):
            self.item = item #为了存储数值数据
            self.next = None #为存储下一个节点的地址
    class Link():
        #构建一个空链表
        def __init__(self):
            #_head要指向第一个节点,如果没有节点则指向None
            self._head = None
    
        def add(self,item):
            #1.创建一个新节点
            node = Node(item)
            node.next = self._head
            self._head = node
        def travel(self):
    #         print(self._head.item)
    #         print(self._head.next.item)
    #         print(self._head.next.next.item)
    
            #在非空的链表中head永远要指向第一个节点的地址,永远不要修改它的指向,否则会造成数据的丢失
            cur = self._head
            while cur:
                print(cur.item)
                cur = cur.next
    
        def is_empty(self):
            return self._head == None
        def length(self):
            cur = self._head
            count = 0#记录节点的个数
            while cur:
                count += 1
                cur = cur.next
            return count
    
        def append(self,item):
            node = Node(item)
            cur = self._head #当前节点
            pre = None #指向cur的前一个节点
            if self._head == None:#如果链表为空则需要单独处理
                self._head = node
                return
    
            while cur:
                pre = cur
                cur = cur.next#循环结束之后cur指向了None,pre指向了最后一个节点
    
            pre.next = node
         # 要查找的item
        def search(self,item):
            cur = self._head# cur为开始节点
            find = False# 查找状态
            while cur:
                if cur.item == item:# 如果cur的值为查找的item值
                    find = True
                    break#退出循环
                else:
                    cur = cur.next# cur为下一个节点的值
            return find
    
        def insert(self,pos,item):
            node = Node(item)
            cur = self._head
            pre = None
    
            #如果插入的位置大于了链表的长度,则默认插入到尾部
            if pos > self.length()-1:
                self.append(item)
                return
    
            for i in range(pos):
                pre = cur
                cur = cur.next
            pre.next = node
            node.next = cur
    
        def remove(self,item):
            cur = self._head
            pre = None
    
            if item == cur.item:
                self._head = cur.next
                return
    
            while cur:
                if cur.item != item:
                    pre = cur
                    cur = cur.next
                else:
                    break
            pre.next = cur.next
    

6.双向链表

双向链表:一种更复杂的链表是 "双向链表" 或 "双面链表"。每个节点有两个链接:一个指向前一个节点,当次节点为第一个节点时,指向空值;而另一个指向下一个节点,当此节点为最后一个节点时,指向空值。

代码实现

# coding=utf-8
# 双向链表

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

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

    def is_empty(self):
        """判断链表是否为空"""
        return self._head is None

    def length(self):
        """获取链表长度"""
        if self.is_empty():
            return 0
        else:
            cur = self._head
            count = 1
            while cur.next is not None:
                count += 1
                cur = cur.next

            return count

    def travel(self):
        """遍历链表"""
        print("↓↓" * 10)
        if self.is_empty():
            print("")

        else:
            cur = self._head
            print(cur.item)
            while cur.next is not None:
                cur = cur.next
                print(cur.item)
        print("↑↑" * 10)

    def add(self, item):
        """链表头部添加节点"""
        node = Node(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 = Node(item)
        if self.is_empty():
            self._head = node
        else:
            cur = self._head
            # 遍历找到最后一个节点
            while cur.next is not None:
                cur = cur.next

            # 在尾节点添加新的节点
            cur.next = node
            node.prev = cur

    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
            cur_pos = 0
            while cur.next is not None:
                if cur_pos == (pos - 1):
                    # 与下一个节点互相指向
                    node.next = cur.next
                    cur.next.prev = node
                    # 与上一个节点互相指向
                    cur.next = node
                    node.prev = cur
                cur_pos += 1
                cur = cur.next

    def remove(self, item):
        """删除节点"""
        if self.is_empty():
            return
        else:
            cur = self._head
            # 删除首节点
            if cur.item == item:
                self._head = cur.next
                cur.next.prev = None

            # 删除其他节点
            else:
                while cur.next is not None:
                    if cur.item == item:
                        # 删除之前:1 ←→ [2] ←→ 3
                        # 删除之后:1 ←→ 3
                        cur.prev.next = cur.next
                        cur.next.prev = cur.prev
                    cur = cur.next

                # 删除尾节点
                if cur.item == item:
                    cur.prev.next = None

    def search(self, item):
        """查找节点是否存在"""
        if self.is_empty():
            return -1
        else:
            cur = self._head
            cur_pos = 0
            while cur.next is not None:
                if cur.item == item:
                    return cur_pos

                cur_pos += 1
                cur = cur.next

            if cur_pos == (self.length() - 1):
                return -1

if __name__ == "__main__":
    ll = DLinkList()
    ll.add(1)       # 1
    ll.add(2)       # 2 1
    ll.append(3)    # 2 1 3
    ll.insert(2, 4) # 2 1 4 3
    ll.insert(4, 5) # 2 1 4 3 5
    ll.insert(0, 6) # 6 2 1 4 3 5
    print("length:", ll.length())   # 6
    ll.travel()                 # 6 2 1 4 3 5
    print("search(3)", ll.search(3))
    print("search(4)", ll.search(4))
    print("search(10)", ll.search(10))
    ll.remove(1)
    print("length:", ll.length())
    ll.travel()
    print("删除首节点 remove(6):")
    ll.remove(6)
    ll.travel()
    print("删除尾节点 remove(5):")
    ll.remove(5)
    ll.travel()

7.面试题:如何将单链表倒序?

def reverse(self):
        if self._head:
            cur = self._head
            pre = None
            cur_next = cur.next

            if cur.next is None:
                return

            while True:
                cur.next = pre

                pre = cur
                cur = cur_next

                if cur == None:
                    self._head = cur
                    break

                cur_next = cur_next.next

            self._head = pre

原文地址:https://www.cnblogs.com/zhangdadayou/p/12070605.html

时间: 2024-10-09 21:54:01

数据结构相关知识的相关文章

深入浅出安卓学习相关知识,如何从零学好移动开发

原文发表自我的个人主页,欢迎大家访问 http://purplesword.info/mobile-develop 由于近几年来互联网的飞速发展,安卓和iOS平台的大量普及推广,移动开发在当前是非常热门的一个方向. 有不少同学问我如何学习安卓,要学些什么,难不难学.之前一直没有想好应该怎么回答这个问题,只是简单的说安卓自身门槛不高,并不难学.因为我觉得准确回答一个类似这样的问题往往需要灵感.现在根据我的学习体验,做个大概的总结. 1.我为什么学安卓 我从刚开始接触安卓开发到现在也有两三年的时间了

链表的相关知识整理

链表的相关知识整理 什么是链表 链表是一种物理存储单元上非连续.非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的.链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成.每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域. 链表与数组的区别 回忆下数组的概念 ,所谓数组,是相同数据类型的元素按一定顺序排列的集合.根据概念我们可以知道数组在内存中连续,链表不连续:由于不同的存储方式导致数组静态分配内存,链表动态分配内存,数组

数据库原理相关知识

数据库原理相关知识 made by @杨领well([email protected]) 一.基础知识 1. 简述数据库系统的特点. 数据结构化 : 这是数据库系统与文件系统的本质区别. 数据的共享性高.冗余度低且易扩充 : 数据共享可以大大减少数据冗余, 节约存储空间.数据共享还能够避免数据之间的不相容性和不一致性. 数据的独立性高 : 数据独立性包括物理独立性和逻辑独立性. 数据由数据库管理系统统一管理和控制 : 数据的安全性保护(保护数据以防止不合法使用造成的数据泄密和破坏).数据的完整性

BI 主要环节 ETL 相关知识

BI架构-BI 主要环节 ETL 相关知识 主要功能  :将源系统的数据加载到数据仓库 及数据集市层中: 主要问题体现:  复杂的源数据环境,包括繁多的数据种类.巨大的加载数据量.错综复杂的数据关系和参差不齐的数据质量 常见术语  ETL:数据抽取.转换.加载(Extract/Transform/Load)  EXF:抽取的文件(Extract File)  CIF:共用接口文件(Common Interface File)  PLF:预加载文件(Preload File)  LDF:

集合类框架_List相关知识

集合类框架是java中比较中重要的框架之一,在Thinking in Java中集合又称为容器. 这篇博文先介绍下List相关知识.List是用于存储序列,它是一个独立的元素序列,这些元素都服从一条或多条规则.List又分为:ArrayList.LinkedList和Vector,而List又属于Collection容器类库. 1.做Java开发,List肯定用过,你都用过哪些List的实现类呢? ArrayList和LinkedList 2.ArrayList和LinkList的底层实现么?

索引相关知识

索引 为什么使用索引 ? 一般的应用系统,读写比例在10:1左右,而且插入操作和一般的更新操作很少出现性能问题,在生产环境中,我们遇到最多的,也是最容易出问题的,还是一些复杂的查询操作,因此对查询语句的优化显然是重中之重.说起加速查询,就不得不提到索引了. ? 索引的目的在于提高查询效率,与我们查阅图书所用的目录是一个道理:先定位到章,然后定位到该章下的一个小节,然后找到页数.相似的例子还有:查字典,查火车车次,飞机航班等,下面内容看不懂的同学也没关系,能明白这个目录的道理就行了. 那么你想,书

python的list相关知识

关于list的相关知识 list01 = ['alex',12,65,'xiaodong',100,'chen',5] list02 = [67,7,'jinjiao_dawang','relax1949',53] #打印list01.list02 print(list01) print(list02) #列表截取.切片 print(list01[1]) print(list01[-2]) print(list01[1:3]) #列表重复 print(list01 * 3) #列表组合 prin

三层交换机相关知识

三层交换机相关知识 这次的作死之路又要开始了.这次的对象主要是交换机:还是三层的: 这是这次实验的总体用图: 现在现根据图上的标志:将所有的主机配置好:目前没有做任何vlan:所以PC1和PC3是能够互通的: 接下来:我想先去探索下三层交换机关闭portswitch会怎么样: 第一步:先关闭了再说: 因为按照图中的设计:PC1的帧如果想要到达PC2,那么就必然要经过LSW1.但是现在我关闭了g0/0/1端口的portswitch:现在pc1并不能ping通pc2: 通过百度:三层交换机的端口不仅

php学习day7--函数的相关知识

今天我们主要学了函数的相关知识,是个比较基础的知识,但也是很重要的. 一.函数 函数就类似于一个工具,我们写好函数之后可以直接进行调用,可以很大的减少代码的从用性,提高页面性能和可读性. 1.函数的定义 在php中函数的定义方式为: function  name($形参1,$形参2.....){ 要执行的代码 return  123: } 在上方的函数定义式中,name代表函数名,小括号内是形参,是用来传递参数,花括号中的就是调用时需要执行的代码. 函数的调用方式: name(实参1,实参2,.