数据结构笔记(二)

数据结构笔记(二)

摘自《大话数据结构》

三、线性表

(一)定义

线性表是零个或多个数据元素的有限序列

若将线性表记为 (a1, a2, a3, …, ai-1, ai, ai+1,…, an) ,则表中 ai-1 领先于 ai ,ai 领先于 ai+1 ,称 ai-1 是 ai 的直接前驱元素,ai+1 是 ai 的直接后继元素。当 i=1, 2, …, n-1 时,ai 有且仅有一个直接后继元素,当 当 i=2, 3, …, n 时,ai 有且仅有一个直接前驱。

(二)抽象数据类型

那么根据上述定义,再依据面向对象思想,对线性表的数据类型进行抽象化。

interface IList {

    /**
     * 将一个已经存在的线性表置成空表。
     */
    public void clear();

    /**
     * 判断线性表中的数据元素个数是否为0,若为0反回true,否则反回false。
     */
    public boolean isEmpty();

    /**
     * 求线性表中的元素个数并返回其值。
     */
    public int length();

    /**
     * 读取并返回线性表中的第i个数据元素的值。其中i的取值范围为0<=i<=length()-1。
     */
    public Object get(int i);

    /**
     * 在线性表的第i个数据元素之前插入一个值为x的数据元素。
     */
    public void insert(int i, Object x);

    /**
     * 删除线性表中第i个数据元素。
     */
    public void remove(int i);

    /**
     * 返回线性表中首次出现的指点数据元素的位序号,若线性表中不包括此数据元素,则返回-1。
     */
    public int indexOf(Object x);

    /**
     * 输出线性表中的各个元素的值。
     */
    public void display();
}

(三)顺序存储结构

3.1、定义

线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。

3.2、存储方式
3.2.1、代码实现

引用一维数组来实现顺序存储结构。

class IIList {
    /**
     * 存储空间初始分配量
     */
    private static final int MAX_SIZE = 20;
    
    /**
     * 数组存储数据元素,最大存储容量为 MAX_SIZE
     */
    Object[] data = new Object[MAX_SIZE];
    
    /**
     * 当前长度
     */
    int length;
}

描述顺序存储结构需要三个属性:

  • 存储空间的起始位置:数组 data ,它的存储位置就是存储空间的存储位置;
  • 线性表的最大存储容量:数组长度 MaxSize ;
  • 线性表的当前长度:length;
3.2.2、数据长度与线性表长度的区别
  • 数据长度:是存放线性表的存储空间的长度,存储分配后这个量一般是不变的。
  • 线性表长度:是线性表中数据元素的个数,随着线性表插入和删除操作的进行,这个量是变化的。

(四)顺序存储结构的插入与删除

4.1、获的元素操作
Object get(int i) {
    if (data.length == 0 || i < 1 || i > data.length) {
        return null;
	}
    return data[i-1];
}
4.2、插入操作
4.2.1、思路
  • 如果插入位置不合理,抛出异常;
  • 如果线性表长度大于等于数组长度,则抛出异常或动态增加容量;
  • 从最后一个元素开始向前遍历到第 i 个位置,分别将它们都向后移动一个位置;
  • 将要插入的元素填入位置 i 处;
  • 表长加 1;
4.2.2、代码实现
public void insert(int i, Object x) throws Exception {
    int k;
    // 顺序线性表已满
    if (data.length == MAX_SIZE) throw new Exception();
    // 当 i 不在范围内时
    if (i < 1 || i > data.length+1) throw new Exception();
    // 若插入数据位置不在表尾
    if (i <= data.length) {
        // 将要插入位置后的数据元素依次向后移动一位
        for (k = data.length-1; k>=i-1; k--) {
            data[k+1] = data[k];
        }
    }
    // 将新元素插入
    data[i-1] = x;
    length++;
}
4.3、删除操作
4.3.1、思路
  • 如果删除位置不合理,抛出异常;
  • 取出删除元素;
  • 从删除位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置;
  • 表长减一;
4.3.2、代码实现
public void remove(int i) throws Exception {
    int k;
    // 线性表为空
    if (data.length == 0) throw new Exception();
    // 删除位置不正确
    if (i < 1 || i > data.length + 1) throw new Exception();
    // 取出要删除的元素
    data[i-1]=null;
    // 如果删除位置不是最后一个
    if (i < data.length) {
        // 将删除位置后继元素依次向前移动一位
        for (k = i; k < data.length; k++) {
            data[k-1] = data[k];
        }
    }
    length--;
}

(五)优缺点

优点缺点
无须为表示表中元素之间的逻辑关系而增加额外的存储空间插入和删除操作需要移动大量元素
可以快速地存取表中任一位置的元素当线性表长度变化较大时,难以确定存储空间的容量
造成存储空间的 “碎片”

四、线性表的链式存储

(一)定义

具有链接存储结构的线性表,它用一组地址任意的存储单元存放线性表中的数据元素,逻辑上相邻的元素在物理上不要求也相邻,不能随机存取。一般用结点描述:结点(表示数据元素) =数据域(数据元素的映象) + 指针域(指示后继元素存储位置)。

对于线性表来说,总得有头有尾,链表也不例外。我们把链表中第一个结点的存储位置叫做头指针,那么整个链表的存取就必须是从头指针开始,之后的每一个结点,其实就是上一个的后继指针指向的位置,最后一个结点指针则为 “空”

有时,为了更加方便地对链表进行操作,会在单链表的第一个结点之前附设一个结点,称为头结点。头结点的数据域可以不存储任何信息,也可以存储如线性表的长度等附加信息,头结点的指针域存储指向第一个结点的指针。

异同头指针头结点
头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针头结点是为了操作的统一和方便而设立,放在第一个元素的结点之前,其数据域一般无意义
头指针具有标识作用,所以常用头指针冠以链表的名字有了头结点,对在第一元素结点前插入结点和删除第一结点,其操作与其他结点就统一了
无论链表是否为空,头指针均不为空。头指针是链表的必要元素头结点不一定是链表的必要要素
class Node {
	Object data;
    Node next;
}
public interface Linked_List {
    
    Node[] content = new Node()[]{};
    
    Node current = null;

    /**
     * 1、求链表长度:以备后续用 思路:遍历一个节点,记录长度加一(注意:初始长度给0)
     *
     * @return int:返回长度
     */
    public int get_length();

    /**
     * 2、添加 思路:找到链表的末尾节点,把新的数据节点添加在后面
     *
     * @param date :插入的数据
     */
    public void add_Node(Node date);

    /**
     * 3、删除 思路:传入下标,根据下标,找到相应的节点,删除节点的前一节点指向删除节点下一节点
     *
     * @param index:删除的数据的下标
     * @return
     */
    public boolean delete_Node(int index);

    /**
     * 4、根据输入数据求下标
     *
     * @param data:输入的数据
     * @return int:返回输入数据下标
     */
    public int get_data(Node data);
    
    public Node first() {
        current = content[0];
        return content[0];
    }
    
    public Node next() {
        return current.getNext();
    }

}

(二)单链表

2.1、读取
2.1.1、思路
  1. 声明一个结点 p 指向链表第一个结点,初始 j 从 1 开始;
  2. 当 j < i 时,就遍历链表,让 p 的指针向后移动,不断的指向下一个结点,j 累加 1;
  3. 若到链表末尾为空,则说明第 i 个元素不存在;
  4. 否则查找成功,返回结点 p 的数据;
2.1.2、代码
Object get_data(int i) throws Exception {
	// 计数器
    int j = 1;
    // 获取第一个结点
    p = first();
    while (p != null & j < i) {
        // 获取下一个结点
        p = next();
        ++j;
    }
    if (p == null || j > i) throw new Exception("你要找的元素不存在");
    // 返回数据
    return p.getData();
}
2.2、插入
2.2.1、思路
  1. 声明一节点 p 指向链表第一个结点,初始化 j 从 1 开始;
  2. 当 j<i 时,就遍历链表,让 p 的指针向后移动,不断指向下一结点, j 累加 1;
  3. 若到链表末尾 p 为空, 则说明第 i 个元素不存在;
  4. 否则查找成功,在系统中生成一个空结点 s;
  5. 将数据元素 e 赋值到 s 结点位置上;
  6. 返回成功;
2.2.2、代码实现
void addElement(Object data, int i) {
    int j = 1;
    Node p = first();
    while (j < i) {
        p = next();
        j++;
    }
    Node insert = new Node(data);
    data.setNext(current.getNext());
    current.setNext(insert);
}
2.3、删除
2.3.1、思路
  1. 声明结点 p 指向链表的第一个元素,初始化 j 从 1 开始;
  2. 当 j<i-1 时,就遍历链表,让 p 的指针向后移动,不断指向下一个结点,j 累加 1;
  3. 若到链表末尾 p 为空,则说明第 i 个元素不存在;
  4. 否则查找成功,将欲删除的结点 p.next() 赋值欲删除结点的前一个结点的指针上;
  5. 返回成功;
2.3.2、代码实现
void deleteElement(int i) {
    Node p = first();
    int j = 1;
    // 遍历到欲删除的前一个结点
    while (j < (i - 1)) {
        p = next();
        j++;
    }
    p.setNext(p.next());
}
2.4、创建
2.4.1、思路
  1. 声明一结点 p 和计数器变量 i;
  2. 初始化一个空链表 l;
  3. 让 l 的头结点指针指向null, 即建立一个带头结点的单链表;
  4. 循环:
    • 生成一新结点赋值给 p;
    • 随机 data 赋值给 p 的数据域;
    • 将 p 插入到头结点与前一新结点之间;
2.4.2、代码实现
void init(Object[] data) {
    Node head = new Node();
    head.setNext(new Node(data[0]));
    for (int i = 1; i < data.length ; i++) {
        Node current = new Node(data[i]);
        currnet.setNext(data[i+1]);
    }
}

五、单链表结构与顺序存储结构的对比

(一)存储分配方式

  • 顺序存储结构用一段连续的存储单元依次存储线性表的数据元素;
  • 单链表采用链式存储结构,用一组任意的存储单元存放线性表的数据元素

(二)时间性能

  • 查找
    • 顺序存储结构 O(1)
    • 链式存储结构 O(n)
  • 插入和删除
    • 顺序存储结构需要平均移动表长一半的元素,时间为 O(n)
    • 单链表在线出某位置的指针后,插入和删除时间仅为 O(1)

(三)空间性能

  • 顺序存储结构需要预分配存储空间大小,分大了,浪费,分小了易发生上溢
  • 单链表不需要分配存储空间大小,只要有就可以分配,元素个数也不受限制

(四)结论

  • 若线性表需要频繁查找,很少进行插入和删除操作时,益采用顺序存储结构。若需要频繁插入和删除时,宜采用单链表结构。
  • 当线性表中的元素个数变化较大或者根本不知道有多大时,最好用单链表结构,这样可以不需要考虑存储空间大小问题。而如果事先知道线性表的大致长度,这种用顺序存储结构效率会高很多。

六、静态链表

(一)定义

用数组描述的链表叫做静态链表

class StaticLinkList {
	public static final int MAX_SIZE = 1000;
    Object[] data;
    int cur;
}

(二)插入

2.1、代码实现

假设 现有一静态链表,其中存放元素 [‘甲’, ‘乙’, ‘丁’, ‘戊’, ‘己’, ‘庚’],实现将元素 ‘丙’ 插入到乙后丁前;

1.	Status ListInsert(StaticLinkList L, int i, ElemType e) {
2.    	int j, k, l;
3.    	k = MAX_SIZE - 1;	// 注意 k  首先是最后一个元素的下标
4.    	if (i < 1 || i > l.length() + 1) return 0;
5.    	j = Malloc_SSL(L);	// 获得空闲分量的下标
7.    	if (j) {
8.	        L[j].data = e;	// 将数据赋值给此分量的 data
9.	        for (l = 1; l <= i - 1; l++) {	// 找到第 i 个元素之前的位置
10.    	        k = L[k].cur;
11.	        }
12.	        L[j].cur = L[k].cur;	// 把第 i 个元素之前的 cur 赋值给新元素的cur
13.	        L[k].cur = j;		    // 把新元素的下标赋值给第 i-1 个元素的cur
14.	        return 1;
15.	    }
16.	    return 0;
17.	}
  • 第 4 行让 $k = $MAX_SIZE = 999 = 999 =999
  • 第 7 行,j = Malloc_SSL(L) = 7。此时下标为 0 的 cur 也因为 7 要被占用而更改备用链表的值为 8;
  • 第 11~12 行,循环 l 由 1 到 2 ,执行两次。代码 k=L[k].cur; 使得 k=999,得到 k = L [ 999 ] . c u r = 1 k=L[999].cur=1 k=L[999].cur=1,再得到 k = L [ 1 ] . c u r = 2 k=L[1].cur=2 k=L[1].cur=2
  • 第 13 行, L [ j ] . c u r = L [ k ] . c u r ; L[j].cur=L[k].cur; L[j].cur=L[k].cur; 因 j=7 ,而 k=2 得到 L[7].cur=L[2]=3。这就是让丙将其 cur 更改为3;
  • 第 14 行,L[k].cur = 7; 意思就是 L[2].cur=7。也就是将 乙的 cur 改为指向 丙 的下标 7;

七、循环链表

(一)定义

将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表。

八、双向链表

(一)定义

双向链表是在单链表的每个结点中,在设置一个指向其前驱结点的指针域。

辽ICP备20007888号
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值