关闭

基本数据结构

标签: 数据结构链表算法队列
299人阅读 评论(0) 收藏 举报
分类:

常用的数据结构有:栈、队列、链表和有根树。本篇博文向大家介绍了栈、队列和链表,关于有根树会在后面关于树的算法中介绍。

栈和队列都是动态集合,且在其上进行delete操作所移除的元素都是预先设定的。在栈中,被删除的是最近插入的元素:栈实现的是一种后进先出策略。类似地,在队列中,被删除的总是在集合中存在时间最长的那个元素:队列实现的是一种先进先出策略。

1 栈

1.1 栈简介

栈上的insert操作称为压入(PUSH),而无元素参数的delete操作称为弹出(POP)。这两个名称使人联想到现实中的栈。盘子从栈中弹出的次序刚好同它们压入的次序相反,这是因为只有最上面的盘子才能被取下来。

1.2 设计栈

1.2.1 栈YJStack

enum YJStackError: ErrorType {
    /// 上溢
    case Overflow
    /// 下溢
    case Underflow
}

///
class YJStack {

    /// 栈,10个位置
    private var list = [Int](count: 10, repeatedValue: 0)
    /// 最新元素的位置
    private var top = -1

}

如代码所示,我们使用list数组模拟栈,并制定栈中只能存放10个数据。在YJStackError的错误信息表示上溢和下溢。下图为大家演示了压入和弹出操作。

1.2.2 空栈

当top=-1时,栈中不包含任何元素,即栈是空的。要测试一个栈是否为空的使用empty(方法)。

// MARK: 栈是否为空
/// 栈是否为空
///
/// - returns: Bool
func empty() -> Bool {
    return self.top == -1
}

1.2.3 弹出(pop)

如果试图对一个空栈执行弹出(pop)操作,则称栈下溢(underfolow),这通常是一个错误。

// MARK: 弹出
/// 弹出
///
/// - returns: Int throws YJStackError
func pop() throws ->Int {
    guard !self.empty() else {
        throw YJStackError.Underflow // 下溢
    }
    self.top--
    return list[self.top+1]
}

1.2.4 压入(push)

如果在压入(push)的时候top超过了list数组的最大下标,即

top > list.count-1

则称栈上溢(overflow)。

// MARK: 压入
/// 压入
///
/// - returns: Int throws YJStackError
func push(x:Int) throws {
    guard self.top + 1 != self.list.count else {
        throw YJStackError.Overflow // 上溢
    }
    self.top++
    list[self.top] = x
}

执行栈的操作时间都为O(1)。

2 队列

2.1 队列简介

队列上的insert操作称为入队(enqueue),delete操作称为出对(dequeue);正如栈的pop操作一样,dequeue操作也没有元素参数。

队列的先进先出特性类似于收银前台排队等待结账的一排顾客。队列有队头(head)和队尾(tail),当有一个元素入队时,它被放在队尾的位置,就像一个新来顾客排在队伍末端一样。而出对的元素则总是在队头的那个,就像排在队伍前面等待最久的那个顾客一样。

2.2 设计队列

2.2.1 队列YJQueue

//
//  YJQueue.swift
//  BasicDataStruct
//
//  CSDN:http://blog.csdn.net/y550918116j
//  GitHub:https://github.com/937447974/Blog
//
//  Created by yangjun on 15/11/17.
//  Copyright © 2015年 阳君. All rights reserved.
//

import Foundation

enum YJQueueError: ErrorType {
    /// 上溢
    case Overflow
    /// 下溢
    case Underflow
}

/// 队列
class YJQueue {

    /// 队列,10个位置
    private var list = [Int](count: 8, repeatedValue: 0)
    /// 队头
    private var head = 0
    /// 队尾
    private var tail = 0

}

我们使用list作为一个队列,最多容纳8个数。该队列有一个head指向队头元素。而属性tail则指向下一个元素将要插入的位置。队列中的元素存放位置为list[head..tail-1],最后head和tail都会移动到第10个位置,此时无法再插入,会发现有8个位置空余了,不利于队列的利用。我们采用环形队列重复利用这8个位置,如下图所示。

这样当head=tail则会有两种情况,一种是空,一种是满。为了解决这种问题,我们在YJQueue添加属性count。

/// 队列中存在的个数
private var count = 0

2.2.2 空队列

当count为0时,队列为空。

// MARK: 队列是否为空
/// 队列是否为空
///
/// - returns: Bool
func empty() -> Bool {
    return self.count == 0
}

2.2.3 入队(enqueue)

如果self.count = self.list.count,此时队列是满的,此时若视图插入一个元素,则队列发生上溢。

// MARK: 入队
/// 入对
///
/// - returns: Int throws YJQueueError
func enqueue(x:Int) throws {
    guard self.count != self.list.count else {
        throw YJQueueError.Overflow // 上溢
    }
    list[self.tail] = x // 存数
    self.count++ // 计数加1
    self.tail++ // 后移动
    self.tail =  self.tail == self.count ? 0 : self.tail // 循环
}

2.2.4 出队(dequeue)

队列是空时,试图从空队列中删除一个元素,则队列发生下溢。

// MARK: 出队
/// 出队
///
/// - returns: Int throws YJQueueError
func dequeue() throws ->Int {
    guard !self.empty() else {
        throw YJQueueError.Underflow // 下溢
    }
    let temp = self.list[self.head]
    self.count--
    self.head++
    self.tail =  self.tail == self.count ? 0 : self.tail
    return temp
}

执行队列的操作时间都为O(1)。

3 链表

3.1 简介

链表(linked list)是一种这样的数据结构,其中的各对象按线性顺序排列。数组的线性顺序是由数组下标决定的,然而和数组不同的是,链表的顺序是由各个对象里的指针决定的。链表为动态集合提供了一种简单而灵活的表示方法。

双向链表(doubly linked list)L的每个元素都是一个对象,每个对象有一个关键字key和两个指针:next和prev。对象中还可以包含其他的辅助数据(或卫星数据)。设x为链表的一个元素,x的next指向它在链表的后继元素,x的prev指向它的前驱元素。

如果x.prev=nil,则元素x没有前驱,因此是链表的第一个元素,即链表的头(head)。如果x.next=nil,则元素x没有后继,因此是链表的最后一个元素,即链表的尾(tail)。属性L.head指向链表的第一个元素。如果L.head=nil,则链表为空。

链表可以有多种形式。它可以是单链接的或双链接的,可以是已排序的或未排序的,可以是循环的或非循环的。

  1. 如果一个链表是单链接的,则省略每个元素中的prev指针。
  2. 如果链表是已排序(sorted)的,则链表的线性顺序与链表中关键字的线性顺序一致;据此,最小的元素就是表头元素,而最大的元素就是表尾元素。
  3. 如果链表是未排序(unsorted),则各元素可以以任何顺序出现。
  4. 在循环链表(circular list)中,头元素的prev指针指向表尾元素,而表尾元素的next指针则指向表头元素。我们可以将循环链表想象成一个各元素组成的圆环。

3.2 链表的优势

相比较普通的线性结构,链表结构的优势有:

  1. 单个节点创建非常方便,普通的线性内存通常在创建的时候就需要设定数据的大小。
  2. 节点的删除非常方便,不需要像线性结构那样移动剩下的数据。
  3. 节点的访问方便,可以通过循环或者递归的方法访问到任意数据,但是平均的访问效率低于线性表。

3.3 设计链表

3.3.1 链表中的元素YJListItem

我们设计的链表是未排序的双向链表。根据相关属性设计链表中的元素。

//
//  YJListItem.swift
//  BasicDataStruct
//
//  CSDN:http://blog.csdn.net/y550918116j
//  GitHub:https://github.com/937447974/Blog
//
//  Created by yangjun on 15/11/17.
//  Copyright © 2015年 阳君. All rights reserved.
//

import Foundation

/// 链表中的元素
class YJListItem {

    /// 关键字
    var key: String?
    /// 前继元素
    var prev: YJListItem!
    /// 后继元素
    var next: YJListItem!

}

3.3.2 链表YJList

这里使用YJList封装链表。

//
//  YJList.swift
//  BasicDataStruct
//
//  CSDN:http://blog.csdn.net/y550918116j
//  GitHub:https://github.com/937447974/Blog
//
//  Created by yangjun on 15/11/17.
//  Copyright © 2015年 阳君. All rights reserved.
//

import Foundation

/// 链表
class YJList {

    /// 哨兵
    private var sentinel = YJListItem()

    // MARK: - 初始化
    init() {
        // 哨兵自循环
        self.sentinel.next = self.sentinel
        self.sentinel.prev = self.sentinel
    }

}

这里你会发现我没有使用表头和表尾。这里设计了一个哨兵(sentinel)对象,其作用是简化边界条件的处理,让代码更加简单。

3.3.3 链表的搜索

过程search(key:String)采用简单的线性搜索方法,用于查找链表YJListItem中第一个关键字的元素,并返回该元素。最坏运行时间为O(n)。

// MARK: 查找YJListItem
/// 根据关键字查找YJListItem
///
/// - parameter key : 关键字
///
/// - returns: key
func search(key:String) -> YJListItem {
    var x = self.sentinel.next
    while x.key != nil && x.key != key {
        x = x.next
    }
    return x
}

3.3.4 链表的插入

给定一个关键字key,方法insert(key:String)将key生成的item连接到链表的前端哨兵后面。运行时间为O(1)。

// MARK: 插入
/// 插入关键字
///
/// - parameter key : 关键字
///
/// - returns: void
func insert(key:String) {
    let item = YJListItem()
    item.key = key
    item.next = self.sentinel.next
    self.sentinel.next.prev = item
    self.sentinel.next = item
    item.prev = self.sentinel
}

3.3.5 链表的删除

方法delete(key:String)将一个关键字key对应的元素从链表YJListItem移除。该过程只需修改其前后元素的指针即可。最坏情况下需要的时间为O(n)。

// MARK: 删除
/// 删除关键字
///
/// - parameter key : 关键字
///
/// - returns: void
func delete(key:String) {
    // 查找元素
    let item = self.search(key)
    if item.key != nil { // 存在则删除
        item.prev.next = item.next
        item.next.prev = item.prev
    }
}

4 小结

本篇博文讲解了一些基本数据结构,其中主要有栈、队列和链表。其中队列介绍了循环队列;链表介绍了包含哨兵的双向无序链表。

 


其他

源代码

https://github.com/937447974/Algorithms

参考资料

算法导论

文档修改记录

时间 描述
2015-11-17 完成关于栈和队列的项目
2015-11-16 完成关于链表的项目和博文

版权所有

CSDN:http://blog.csdn.net/y550918116j

GitHub:https://github.com/937447974/Blog

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:208453次
    • 积分:6157
    • 等级:
    • 排名:第4163名
    • 原创:381篇
    • 转载:4篇
    • 译文:0篇
    • 评论:17条
    博客专栏
    最新评论