【数据结构与算法】之线性表的应用和操作

数据结构概念

  • 数据结构:是相互之间存在一种或多种特定关系的数据元素的集合。
  • 数据结构的逻辑结构:数据对象中数据元素之间的相互关系,分为线性结构、树形结构、图形结构以及集合结构。
  • 数据结构的物理结构:数据的逻辑结构在计算机中的存储形式,分为顺序存储和链式存储(不连续存储)。
  • 算法:解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作。
  • 算法五个基本特性:输入、输出、有穷性、确定性和可行性
  • 算法时间复杂度O(n):常数阶、线性阶、平方阶、对数阶、立方阶、nlogn阶、指数阶。
  • 耗时排序:O(1) < O(logn) < O(n) < O(nlgn) < O(x2) < O(x3) < O(2n) < O(n!) < O(nn)。

线性表

一、概念
  • 线性表:就是零个或者多个数据元素的有限序列,数据元素之间是一对一的关系;
  • 性质:数据元素可以为空;数据元素有限;数据元素之间的逻辑结构为线性结构,也就是一对一的关系;数据元素类型相同。
线性表的抽象数据类型:
ADT 线性表(List)
Data
      线性表的数据对象集合为{a1, a2, ......, an},每一个元素的类型都是DataType。其中,除第一个元素a1外,每一个元素有且仅有一个直接前驱元素,除了最后一个元素an外,每一个元素有且仅有一个直接后续元素。数据元素之间的关系是一对一的关系。
Operation
       count:线性表元素个数。
       first:头指针。
       last:尾指针。
       isEmpty():若线性表为空,返回true,否则返回false。
       remove():将线性表清空
       node(i):将线性表中的第i个位置的元素返回。
       insert(data,i):在线性表中的第i个位置插入新数据data。
EndADT
二、顺序线性表
  • 顺序线性表:使用一段连续的地址存储单元放置线性表的数据元素。
  • 顺序存储的插入步骤:
    ① 线性表长度大于等于数组长度,抛出异常;
    ② 插入位置不合适,抛出异常(判断插入位置与0和最大值的大小);
    ③ 从最后一个元素开始向前变量,将它们都向后移动一位;
    ④ 将要插入的元素填入指定位置;
    ⑤ 表长加一;
  • 顺序存储的删除步骤:
    ① 线性表是否为空;
    ② 删除位置不合适,抛出异常(判断插入位置与0和最大值的大小);
    ③ 取出删除元素;
    ④ 从删除元素的位置遍历到最后一个元素位置,将它们前移一位;
    ⑤ 表长减一;
  • 顺序线性表的优缺点:
优点缺点
可以快速获取下标的数据元素,时间复杂度为O(1)插入和删除操作需要移动大量的元素,时间复杂度为O(n)
逻辑关系是一对一的关系,连续存储单元足以储存,不需要增加额外的存储空间线性表的存储空间大小难以确定,并且不好扩展
造成存储空间碎片
三、链式线性表
  • 链式线性表:线性表的数据元素可以存储在随意的存储单元,每一个节点不仅仅包括数据元素还有一个指向下一个节点的指针(基本的单链表)。
  • 链式(单链表)和顺序线性表优缺点对比:
链式线性表顺序线性表
存储方式任意地址存储空间一段地址连续的存储空间
时间性能(查找)O(n)O(1)
时间性能(插入和删除)寻找相应的节点,时间复杂度为O(n),然后,插入和删除为O(1)O(n)
空间性能不需要提前分配空间,只要有存储空间分配就行,数据元素个数只受可分配存储空间大小的限制需要提前分配存储空间,分配大了,浪费空间,分配小了,容易发生上溢
  • 链式线性表的基本分类:
名称描述
单向链表一段地址连续的存储空间
静态链表使用顺序结构实现链式线性表
双向链表每个节点除了数据元素,还包含一个指向上一个节点的指针和一个指向下一个节点的指针
循环链表线性表的尾部指向头节点,形成一个闭环
四、双向链表
  • 节点定义:
public class LinkedListNode<T> {
    var data: T  //Data could not be nil.
    var previous: LinkedListNode?  //The pointer to previous node.
    var next: LinkedListNode?  //The pointer to next node.
    init(_ data: T) {
        self.data = data
    }
}
  • 双向链表:head指向第一个有数据的节点,有的线性表会生成一个头节点,该节点不存储任何数据或者只存储该链表的长度,该节点指向第一个有数据的节点。这样做的好处就是,第一个节点的删除和插入操作和其他节点保持一致。
public enum ErrorStatus {
    case Error(message: String)
    case OK
}

public class DoubleLinkedList<T> {
    public typealias Node = LinkedListNode<T>
    
    private var head: Node?  //Head node of link list.
    
    public var isEmpty: Bool {  //If link list has no data, return true.
        return head == nil
    }
    
    public var first: Node? {  //Get first node is the head of link list.
        return head
    }
    
    public var last: Node? {  //Last node of link list.
        ...
        return node
    }
    
    public var count: Int {  //Retrun link list's nodes count.
        ...
        return count
    }
    
    public func node(atIndex index: Int) -> Node? {  //Get node with index
        ...
        return node
    }
    
    public func appendData(data: T) {  //Append data to link list tail
        ...
    }
    
    public func insert(data: T, atIndex index: Int) -> ErrorStatus {  //Insert data at index
        guard index >= 0, index <= count else {
            return ErrorStatus.Error(message: "Index is out of range!")
        }
        let newNode = Node(data)
        if index == 0 {
            if let node = first {
                head = newNode
                newNode.next = node
                node.previous = newNode
            } else {
                head = newNode
            }
        } else {
            let node = self.node(atIndex: index-1)
            let nextNode = self.node(atIndex: index)
            node?.next = newNode
            newNode.previous = node
            newNode.next = nextNode
            nextNode?.previous = newNode
        }
        return ErrorStatus.OK
    }
    
    public func remove(atIndex index: Int) -> (T?, ErrorStatus) {  //Remove node at index
        guard !isEmpty else {
            return (nil, ErrorStatus.Error(message: "Link list is Empty!"))
        }
        
        guard index >= 0, index < count else {
            return (nil, ErrorStatus.Error(message: "Index is out of range!"))
        }
        
        let node = self.node(atIndex: index)
        let nextNode = self.node(atIndex: index+1)
        if index == 0 {
            head = nextNode
        } else {
            let beforeNode = self.node(atIndex: index-1)
            beforeNode?.next = nextNode
            nextNode?.previous = beforeNode
        }
        return (node?.data, ErrorStatus.OK)
    }    
}
  • insert操作:无论insert还是remove都是先拆链,然后再组合成新的数据链。
    ① 先判断需要插入数据的index是否在[0, count]的范围之内,注意这里是方括号,也就是包含边界,因为线性表最前面和最后面都可以插入新的数据;
    ② 生成新节点;
    ③ 因为这里的双向链表没有采取头节点的方式实现,所以,插入第一个节点和其他节点有点不一样,需要做一些判断;
    ④ 如果是插入第一个节点,则判断如果该链表为空,则直接设置head=newNode;如果该链表不为空,则将一个节点赋值给node,然后将newNode赋值给head,接着将node赋值给newNode.next,最后设置node.previous=newNode;
    ⑤ 如果不是插入一个节点,则先获取下标为index-1的节点node,然后获取下标为index的节点nextNode。设置node.next=newNode,然后newNode.next=nextNode,连成一条指向下一个数据元素的链,最后设置newNode.previous=node和nextNode.previous=newNode连上指向上一个数据元素的链,自此,先的数据插入成功;
public func insert(data: T, atIndex index: Int) -> ErrorStatus {  //Insert data at index
    guard index >= 0, index <= count else {
        return ErrorStatus.Error(message: "Index is out of range!")
    }
    let newNode = Node(data)
    if index == 0 {
        if let node = first {
            head = newNode
            newNode.next = node
            node.previous = newNode
        } else {
            head = newNode
        }
    } else {
        let node = self.node(atIndex: index-1)
        let nextNode = self.node(atIndex: index)
        node?.next = newNode
        newNode.next = nextNode
        newNode.previous = node
        nextNode?.previous = newNode
    }
    return ErrorStatus.OK
}
  • remove操作:
    ① 先判断是否是空链,如果是则返回,否则再判断需要删除数据的小表是否在合理范围内,如果不是则返回;
    ② 判断index是否等于0,如果是,则直接将head=secondNode;
    ③ 获取beforeNode和nextNode,然后将beforeNode.next=nextNode,nextNode,previous=beforeNode,自此,下标为index的节点,没有任何对象指向它,在当前函数域外就外被系统回收掉;
public func remove(atIndex index: Int) -> (T?, ErrorStatus) {  //Remove node at index
    guard !isEmpty else {
        return (nil, ErrorStatus.Error(message: "Link list is Empty!"))
    }
    
    guard index >= 0, index < count else {
        return (nil, ErrorStatus.Error(message: "Index is out of range!"))
    }
    
    let node = self.node(atIndex: index)
    let nextNode = self.node(atIndex: index+1)
    if index == 0 {
        head = nextNode
    } else {
        let beforeNode = self.node(atIndex: index-1)
        beforeNode?.next = nextNode
        nextNode?.previous = beforeNode
    }
    return (node?.data, ErrorStatus.OK)
}
五、总结
  • 若线性表需要频繁查找,很少进行插入和删除操作时,使用顺序存储结构;反之,使用链式存储结构。
  • 如果提前知道线性表需要的存储空间,可以使用顺序结构;如果不知道线性表中的数据元素变化有多大,即不确定需要多大的存储空间,则使用链式存储结构。
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

╰つ栺尖篴夢ゞ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值