数据结构——线性表

目录

什么是线性表(Linear List)?

线性表的存储结构:

顺序存储

链式存储

链表带头结点和不带头结点的区别?

链表的基本操作(以单链表为例)       

双链表

 双链表的插入

双链表的删除

循环单链表 

循环双链表

插入操作 

删除操作 

静态链表​


 

什么是线性表(Linear List)?

首先特别注意:线性表是一种逻辑结构,表示元素之间一对一的相邻关系。顺序表和链表是指存储结构,两者属于不同层次的概念,不要将其混淆。

线性表是具有相同数据类型的 n(n>=0)个数据元素的有限序列,其中 n 为表长,当 n=0 时线性表是一个空表。若用 L 命名线性表,其一般表示为:

L = (a1,a2,...,ai,ai+1,...,an)


式中,a1 是唯一的“第一个”数据元素,又称表头元素;an 是唯一的“最后一个”数据元素,又称表尾元素

除第一个元素外,每个元素有且只有一个直接前驱。除最后一个元素外,每个元素有且只有一个直接后继。

线性表具有如下特点:

表中元素的个数有限
表中元素具有逻辑上的顺序性,表中元素有其先后次序
表中元素都是数据元素,每个元素都是单个元素
表中元素的数据类型都相同,每个元素占用相同大小的存储空间
表中元素具有抽象性,只讨论元素间的逻辑关系,不考虑元素表示的内容

再次注意:线性表是一种逻辑结构,表示元素之间一对一的相邻关系。顺序表和链表是指存储结构,两者属于不同层次的概念,不要将其混淆。 

线性表的存储结构:

顺序存储

线性表的顺序存储又称顺序表。是用一组地址连续的存储单元依次存储线性表中的数据元素,使得逻辑上相邻的两个元素在物理位置上也相邻。顺序表中元素的逻辑顺序与其物理顺序相同(这是顺序表的特点)

注意:线性表中元素的次序(也叫位序)是从 1 开始的,数组中元素的下标是从 0 开始的

顺序表具有的特点如下所示:

  • 随机访问,即通过首地址和元素序号可在时间 O(1) 内找到指定的元素
  • 存储密度高,每个节点只存储数据元素
  • 逻辑上相邻的两个元素在物理上也相邻,插入和删除数据时需要移动大量元素

链式存储

线性表的链式存储又称单链表,是指通过一组任意的存储单元来存储线性表中的数据元素。

单链表可以解决顺序表需要大量存储单元的缺点,但单链表附加指针域,也存在浪费存储空间的缺点。单链表的存储空间离散地分布在存储空间中,单链表是非随机存取地存储结构。在使用单链表查找数据元素时,不能直接找到某个特定地结点,需要从表头开始遍历,依次比较查找。

链表带头结点和不带头结点的区别?

头指针:通常使用“头指针”来标识一个链表,如单链表L,头指针为NULL的时表示一个空链表。链表非空时,头指针指向的是第一个结点的存储位置。

头结点:在单链表的第一个结点之前附加一个结点,称为头结点。头结点的Data域可以不设任何信息,也可以记录表长等相关信息。若链表是带有头结点的,则头指针指向头结点的存储位置。

[注意]无论是否有头结点,头指针始终指向链表的第一个结点。如果有头结点,头指针就指向头结点。

两者区别

1、不带头结点的单链表对于第一个节点的操作与其他节点不一样,需要特殊处理,这增加了程序的复杂性和出现bug的机会,因此,通常在单链表的开始结点之前附设一个头结点,可以理解为头结点是为了方便增删等操作的。

2、带头结点与不带头结点初始化、插入、删除、输出操作都不一样,在遍历输出链表数据时,带头结点的判断条件是while(head->next!=NULL),而不带头结点是while(head!=NULL),虽然头指针可以在初始时设定,但是如1所述,对于特殊情况如只有一个节点会出现问题。

链表的基本操作(以单链表为例)       

        1. 创建

(1)头插法创建单链表

从一个空表开始,生成新结点,并将读取到的数据存储到新结点的数据域中,然后将新结点插入到当前链表的表头,即头结点之后。头插法操作示意图如下所示:

 

头插法建立单链表时,读入数据的顺序与生成的链表中的元素的顺序是相反的。每个结点的插入时间为 O(1),设单链表长为 n,则总时间复杂度为 O(n)。

注意:使用头插法创建单链表可以实现链表的逆序问题!(比如待插入的节点顺序是5 - 2 - 0 - 1 使用头插法后顺序就变为1 - 0 - 2 - 5)

(2)尾插法创建单链表

  • 该方法将新节点插入到当前链表的表尾中,为此必须增加一个尾指针 r,使其始终指向当前链表的尾结点。该方法生成的链表中的结点的次序和输入数据的顺序一致。尾插法创建单链表示意图如下图所示:
  • 在这里插入图片描述
  • 附设了一个指向表尾结点的指针,故时间复杂度和头插法的相同。

        2. 销毁

        3. 增删改查等操作

        下面是创建单链表的基本代码实现(尾插法):

/**
 * @description: 创建链表单节点类
 * @param {*} val 节点值
 * @return {*}
 */
 function ListNode(val) {
    this.val = val
    this.next = null
}
/**
 * @description: 创建链表类
 * @param {*}
 * @return {*}
 */
function LinkedList() {
    this.length = 0
    this.head = null
}

// 向链表中追加节点
LinkedList.prototype.append = function (val) { 
    //创建所需要追加的节点
    let node = new ListNode(val)
    
    // 判断节点是否为空
    if (!this.head) {
      this.head = node  
    }
    // 这里调用前面的getElementLoc函数找到尾节点
    let tail = this.getElementLoc(this.length - 1)
    tail.next = node
    this.length++
}
 
// 在链表的指定位置插入节点
LinkedList.prototype.insert = function (index, val) { 
    if (index < 0 || index > this.length) return  false
    let node = new ListNode(val)
    if (index === 0) {
        //这里的等号不好理解,就理解为:从等号左边的节点指向等号右边的节点
        node.next = this.head
        this.head = node
    }else {
        let preN = this.getElementLoc(index - 1)
        node.next = preN.next
        preN.next = node
    }
    this.length++
    return true
}
 
// 删除链表中指定位置的元素,并返回这个元素的值
LinkedList.prototype.removeLoc = function (index) { 
    if(index < 0 || index > this.length) return false

    let cur = this.head

    if(index === 0) {
        this.head = cur.next
    }else {
        let preN = this.getElementLoc(index - 1)
        cur = preN.next
        preN.next = cur.next
    }
    this.length--
    return cur.val
}
 
// 删除链表中对应的元素
LinkedList.prototype.remove = function (val) { 
    let index = this.indexOf(val)
    return this.removeLoc(index)
}
 
// 获取链表中给定元素的索引
LinkedList.prototype.indexOf = function (val) { 
    let cur = this.head

    for(let i = 0; i<this.length; i++) {
        if(cur.val === val) return i
        cur = cur.next
    }
    //找不到返回-1
    return -1
}
 
// 获取链表中某个节点
LinkedList.prototype.find = function (val) { 
    // 这里的逻辑和getElementLoc类似都是从头开始遍历节点
    let cur = this.head
    while (cur) {
        if(cur.val === val) return cur
        cur = cur.next
    }
    return null
}
 
// 获取链表中索引所对应的元素
LinkedList.prototype.getElementLoc = function (index) { 
    if (index < 0 || index >= this.length) return null
    if(index === 0) return this.head
    // 新建一个节点指向头节点
    let cur = this.head
    //链表查找指定位置元素的复杂度是O(n),因为查找必须从头开始
    // 下面这种写法不好理解:其实就是遍历index个节点找到目标节点
    // while (index--) {
    //   cur = cur.next
    // }

    let i = 1//记录第一个节点下标
    // 遍历循环链表
    while (cur && i<=index) { //当前节点不为空且节点下标小于等于index
        cur = cur.next
        i++
    }
    return cur
}
 
// 判断链表是否为空
LinkedList.prototype.isEmpty = function () { 
    return !this.length
}
 
// 获取链表的长度
LinkedList.prototype.size = function () { 
    return this.length
}
 
// 获取链表的头元素
LinkedList.prototype.getHead = function () { 
    return this.head
}
 
// 清空链表
LinkedList.prototype.clear = function () { 
    this.head = null
    this.length = 0
}
 
// 序列化链表
LinkedList.prototype.join = function (string) { 
    // 序列化链表即使用指定格式输出链表,类似于数组中 join 方法,此举旨在便于我们测试
    let cur = this.head
    let str = ''
    while (cur) {
        str += cur.val
    
        if (cur.next) str += string
    
        cur = cur.next
    }
    return str
}

双链表

为了克服单链表只能从头开始遍历,引入了双链表。(有prior指针指向前驱结点)

 双链表的插入

注意第一二步操作必须在第四步操作之前,否则p结点的后继结点的指针会丢掉,导致插入失败(s节点的后继结点会指向自己本身,导致p结点后面的数据都会丢失!

 

双链表的删除

循环单链表 

循环双链表

插入操作 

 

删除操作 

 

 

静态链表

 

其实栈,队列和串之类的数据结构也是一种操作受限特殊的线性表,只不过他们在操作上不同于线性表。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值