what's the 数据结构

目录

      

      队列

      链表双向链表

      哈希表

        二叉搜索树


what's the 数据结构

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

  通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法和索引技术有关。

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

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

下面我来重点来学习数据结构中比较重要的几种:栈、队列、链表、哈希表、二叉搜索树

  栈(Stack)是一个数据集合,它是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。可以将栈理解为只能在一端进行插入或删除操作的列表。

  栈的特点:后进先出、先进后出(类似于往箱子里放东西,要拿的时候只能拿最上面的而最上面的是最后进的)

  栈操作:进栈push、出栈pop、取栈顶gettop(在Python中,不用自定义栈,直接用列表就行,进栈函数:append 出栈函数:pop 查看栈顶函数:li[-1])

栈的应用

  栈可以应用在处理括号匹配问题——括号匹配问题:给一个字符串,其中包含小括号、中括号、大括号,求该字符串中的括号是否匹配。

基本思路:按顺序遍历字符串是左括号则进栈,来的是右括号则将栈顶左括号pop,若来的右括号与栈顶左括号不匹配或空栈情况下来了右括号则返回错误信息

def brace_match(s):
    stack = []
    match = {')':'(', ']':'[', '}':'{'}
    match2 = {'(':')', '[':']', '{':'}'}
    for ch in s:
        if ch in {'(', '[', '{'}:
            stack.append(ch)#左括号入栈
        elif len(stack) == 0:
            print("缺少%s" % match[ch])#栈空了,但却来了一个右括号
            return False
        elif stack[-1] == match[ch]:#栈顶左括号与来的右括号相匹配
            stack.pop()#出栈
        else:
            print("括号不匹配")
            return False
    if len(stack) > 0:#全部操作完后栈内还有剩余左括号
        print("缺少%s" % (match2[stack[-1]]))#显示栈顶左括号对应的右括号
        return False
    print('合法')
    return True


brace_match("[{()[]}{}{}")#缺少]

队列

  队列(Queue)是一个数据集合,仅允许在列表的一端进行插入,另一端进行删除。 进行插入的一端称为队尾(rear),插入动作称为进队或入队;进行删除的一端称为队头(front),删除动作称为出队。和栈一样,队列是一种操作受限制的线性表。

  队列的性质:先进先出(可以将队列理解为排队买东西)

  特殊情况——双向队列:队列的两端都允许进行进队和出队操作。

如何用列表实现队列:

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

以上就是队列实现的基本思路,但是其实队列的实现原理是一个环形队列

环形队列:当队尾指针front == Maxsize + 1时,再前进一个位置就自动到0。

  • 实现方式:求余数运算
  • 队首指针前进1:front = (front + 1) % MaxSize
  • 队尾指针前进1:rear = (rear + 1) % MaxSize
  • 队空条件:rear == front
  • 队满条件:(rear + 1) % MaxSize == front

 

在Python中,有一个内置模块可以帮我们快速建立起一个队列——deque模块

"""
使用方法:from collections import deque
创建队列:queue = deque(li)
进队:append()
出队:popleft()
双向队列队首进队:appendleft()
双向队列队尾进队:pop()
"""

栈和队列的知识点应用:

  用栈和队列的知识来解决迷宫问题:http://www.cnblogs.com/zhuminghui/p/8414307.html

链表

  链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。
  每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。
  使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。链表最明显的好处就是,常规数组排列关联项目的方式可能不同于这些数据项目在记忆体或磁盘上顺序,数据的存取往往要在不同的排列顺序中转换。链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。链表有很多种不同的类型:单向链表,双向链表以及循环链表。链表可以在多种编程语言中实现。像Lisp和Scheme这样的语言的内建数据类型中就包含了链表的存取和操作。程序语言或面向对象语言,如C,C++和Java依靠易变工具来生成链表。

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

#结点的定义
class Node(object):
    def __init__(self, item):
        self.item = item
        self.next = None

  建立链表的方式有头插法和尾插法两种

  • 头插法:在一个结点的前面插入元素,head的指针由指向原来的结点变为指向新元素,新元素的指针指向原来的结点
  • 尾插法:在一个元素后面插入一个元素,原来结点的指针指向新元素

建立列表实现代码如下:

#定义结点
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None
#头插法
def create_linklist(li):
    head = None
    for num in li:
        node = Node(num)
        node.next = head
        head = node
    return head
#尾插法
def create_linklist_tail(li):
    head = None
    if not li:
        return head
    head = Node(li[0])
    tail = head
    for num in li[1:]:
        node = Node(num)
        tail.next = node
        tail = node
    return head


def print_linklist(head):
    node = head
    while node:
        print(node.data)
        node = node.next

linklist = create_linklist_tail([1,2,3,4])
print_linklist(linklist)
链表结点的插入
  链表插入结点的操作的重点是指针的变换,首先我们有两个结点A指向B,这时要在AB中间插入C,我们需要将C的指针指向B,然后将A的指针指向C,在删除AB之间的指针,就完成了C的插入,由AB变为了ACB
                
#curNode为A结点
c.next = curNode.next
curNode.next = c
链表结点的删除
  在链表中,要删除一个结点不能直接删掉就万事大吉,我们需要将指向该结点的结点的指针指向该结点指针指向的结点(A指向B指向C,B为要删除的该结点,将A的指针指向C),然后才能删除该节点(B)
                 
#p为要删除的结点
p = curNode.next
curNode.next = curNode.next.next#即p.next
del p
链表的特殊形态——双链表
  双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继(后面结点)和直接前驱(前面结点)。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。 
双向链表的节点定义:
class Node(object):
    def __init__(self, item=None):
        self.item = item
        self.next = None
        self.prior = None

双向链表结点的插入

  与链表相同,双向链表插入结点也需要将指针进行变换。同样是AB之间要插入C,我们需要先将C的指针指向B、B的指针由指向A转变为指向B,然后C的另一个指针指向A,A结点的指针由指向B转变为指向B。
    
#p为新插入的元素
p.next = curNode.next
curNode.next.prior = p
p.prior = curNode
curNode.next = p
双向链表结点的删除
  删除双向链表的结点前需要建立起该结点前后两个结点的指针关系,然后才能删除结点
#p为要删除的结点
p = curNode.next
curNode.next = p.next
p.next.prior = curNode
del p
链表的复杂度分析
  • 按元素值查找——O(n),因为没有下标所以没法做二分
  • 按下标查找——O(n),因为没有下标
  • 在某元素后插入——O(1)
  • 删除某元素——O(1)
哈希表 

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

  假设有一个长度为7的数组,哈希函数h(k)=k%7。元素集合{14,22,3,5}的存储方式如下图。

哈希冲突:

  由于哈希表的大小是有限的,而要存储的值的总数量是无限的,因此对于任何哈希函数,都会出现两个不同元素映射到同一个位置上的情况,这种情况叫做哈希冲突。

比如上图中的哈希表就存在这哈希冲突——h(k)=k%7, h(0)=h(7)=h(14)=...

解决哈希冲突方法
方法一:开放寻址法——如果哈希函数返回的位置已经有值,则可以向后探查新的位置来存储这个值。
  • 线性探查:如果位置i被占用,则探查i+1, i+2,……
  • 二次探查:如果位置i被占用,则探查i+12,i-12,i+22,i-22,……
  • 二度哈希:有n个哈希函数,当使用第1个哈希函数h1发生冲突时,则尝试使用h2,h3,……
方法二:拉链法——哈希表每个位置都连接一个链表,当冲突发生时,冲突的元素将被加到该位置链表的最后。

哈希表在Python中的应用

#字典与集合都是通过哈希表来实现的
#在Python中的字典:
a = {'name': 'Damon', 'age': 18, 'gender': 'Man'}
#使用哈希表存储字典,通过哈希函数将字典的键映射为下标。假设h(‘name’) = 3, h(‘age’) = 1, h(‘gender’) = 4,则哈希表存储为[None, 18, None, ’Damon’, ‘Man’]
#在字典键值对数量不多的情况下,几乎不会发生哈希冲突,此时查找一个元素的时间复杂度为O(1)。
二叉搜索树 
  二叉搜索树知识点地址:http://www.cnblogs.com/zhuminghui/p/8409508.html
 
 
 
 
 
 
                           
原文地址:https://www.cnblogs.com/zhuminghui/p/8414317.html