【期末不挂科 数据结构】第二章 线性表

目录

2.1 线性表的定义及其逻辑结构

2.2 线性表的顺序存储和实现

2.3 线性表的链式存储和实现

2.4 顺序表和链表的比较

2.5 例题

2.6 写在最后


首先说明一下 期末不挂科 系列专为零基础计算机小白所写,因而不会涉及太多较难问题,基本上都是书本中的知识点和一些简单例题,建议搭配系列习题(会有详解~)作为巩固练习,预祝大家期末考试都不挂科,取得一个好成绩~

本章我们一起来学习一下线性表吧!(本章节的学习内容会有点多,但其实认真看的话还是很简单的,要坚持学下去哦,加油~)

线性表是一种最常用、最简单,也是最基本的数据结构,他是学习其他数据结构的基础。线性表在计算机中可以用顺序存储链式存储两种存储结构来表示。其中用顺序存储结构表示的线性表称为顺序表,用链式存储结构表示的线性表称为链表。链表又有单链表、双向链表、循环链表之分。

2.1 线性表的定义及其逻辑结构

线性表的定义:线性表是由n(n>=0)个类型相同的数据元素组成的有限序列,记作:

来看一张图理解一下吧: 

 小知识点来喽~

①元素个数n表的长度n=0时为空表

②ai是表中第i个元素,i是其位序a0为第0个元素

③ai-1和ai(0<=i<n)之间存在序偶关系<ai-1,ai>

        ai的直接前驱是ai-1,ai-1的直接后继是ai

        a0无直接前驱称为表头元素,an-1无直接后继称为表尾元素

接下来我们来学习一下线性表的基本操作吧:

// 线性表的抽象数据结构Java接口描述如下:
public interface Ilist{
    public void clear();    // clear() 将一个已经存在的线性表置成空表
    public boolean isEmpty();  // isEmpty() 判断线性表是否为空,若为空则返回true;否则返回false
    public int length();    // length() 求线性表中数据元素个数并返回其值
    public Object get(int i); // get(i) 读取并返回线性表中第i个元素的值。其中i的取值范围为0<=i<=length()-1
    public void insert(int i,Object x); // insert(i,x) 在线性表的第i个数据元素之前插入一个值为x的数据元素。其中取值范围为0<=i<=length()。当i=length()时,在表尾插入x
    public void remove(int i); // remove(i) 删除并返回线性表中第i个元素。其中i的取值范围为0<=i<=length()-1
    public int indexOf(Object x); // indexOf(x) 返回线性表中首次出现指定数据元素的位序号,若线性表中不包含此数据元素,则返回-1
    public void display(); // display() 输出线性表中的各个数据元素的值
}

2.2 线性表的顺序存储和实现

线性表的顺序存储结构称为顺序表。数据元素按照其逻辑次序依次存放到一片连续的地址空间,就形成了顺序表

顺序表存储示意图如下:

(a0,a1...an-1依序排至0,1...n-1位置上,原来在逻辑上相邻的数据元素在数组中也是相邻的)

 由此可知,数据元素在内存的物理存储次序反映了它们之间的逻辑关系。(物理上相邻那么他们逻辑上也相邻

顺序表是随机存储结构

小说明:

(1 ) SeqList<T>为泛型类,T表示顺序表数据元素的数据类型,创建该类实例时,T的实际参数为一个确定的类。
例如:String values[ ]={"A””B"”,"C”,"D”,”E”};
         SeqList<String> lista=new SeqList<String>(values);

 (2) Java的类是引用数据类型,一个对象引用一个实例。因此两个对象间的赋值是引用赋值,传递的值是对象引用,使得两个对象引用同一个实例。

我们先来学习一下顺序表的三种构造方法吧:

// 方法一
public SeqList(int length) // 构造容量为length的空表
{
    this.element = new Object[length]; // 申请数组的存储空间,元素为null
    this.n = 0;
}

// 方法二
public SeqList() // 创建默认容量的空表
{
    this(64);
/调用本类已声明的指定参数列表的构造方法
}

// 方法三
public SeqList(T[] values) // 构造顺序表,由values数组提供元素,忽略其中空对象
{
    this(values.length);  // 创建容量为values.length的空表
    for (int i=0; i<values.length; i++) // 复制数组元素,O(n)
        this.element[i] = values[i]; // 对象引用赋值
    this.n = element.length;
}

接下来我们来看一下插入、删除、查找操作算法:

①顺序表的插入操作算法

public void insert(int i,Object x) throws Exception{
    if(curLen == listElem.length)      // 判断顺序表是否已满
        throw new Exception("顺序表已满");   // 抛出异常
    if(i<0 || i > curLen)  // i 不合法
        throw new Exception("插入位置不合法"); // 抛出异常
    for(int j = curLen; j > i ; j--)
        listElem[j] = listElem[j - 1]; // 插入位置及其之后的所有数据元素后移一位
    listElem[i] = x; // 插入x
    curLen++;  // 表长加1
} // 算法结束

②顺序表的删除操作算法

public void remove(int i) throws Exception{
    if(i<0 || i > curLen - 1)  // i 不合法
        throw new Exception("删除位置不合法"); // 抛出异常
    for(int j = i; j < curLen - 1; j++)
        listElem[j] = listElem[j + 1]; // 被删除元素之后的所有数据元素后移一位
    curLen--; // 表长减1
} // 算法结束

③顺序表的查找操作算法

public int indexOf(Object x){
    int j = 0; // j 指示顺序表中待比较的数据元素,其初始值指示顺序表中第0个数据元素
    while(j < curLen &&!listElem[j].equals(x)) // 依次比较
        j++;
    if(j < curLen) // 判断 j 的位置是否位于顺序表中
        return j; // 返回值为 x 的数据元素在顺序表中的位置
    else
        return - 1; // 值为 x 的数据元素在顺序表中不存在
} // 算法结束

顺序线性表及其基本操作 

package ch02;

/**
 * 
 * 顺序线性表及其基本操作
 * 
 */
public class SqList implements IList {
	private Object[] listElem; // 线性表存储空间

	private int curLen; // 当前长度

	// 顺序表的构造函数,构造一个存储空间容量为maxSize的线性表
	public SqList(int maxSize) {
		curLen = 0; // 置顺序表的当前长度为0
		listElem = new Object[maxSize];// 为顺序表分配maxSize个存储单元
	}

	// 将一个已经存在的线性表置成空表
	public void clear() {
		curLen = 0; // 置顺序表的当前长度为0

	}

	// 判断当前线性表中数据元素个数是否为0,若为0则函数返回true,否则返回false
	public boolean isEmpty() {
		return curLen == 0;
	}
	
	
	// 求线性表中的数据元素个数并由函数返回其值
	public int length() {
		return curLen; // 返回顺序表的当前长度
	}

	// 读取到线性表中的第i个数据元素并由函数返回其值。其中i取值范围为:0≤i≤length()-1,如果i值不在此范围则抛出异常
	public Object get(int i) throws Exception {
		if (i < 0 || i > curLen - 1) // i小于0或者大于表长减1
			throw new Exception("第" + i + "个元素不存在"); // 输出异常

		return listElem[i]; // 返回顺序表中第i个数据元素
	}

	// 在线性表的第i个数据元素之前插入一个值为x的数据元素。其中i取值范围为:0≤i≤length()。如果i值不在此范围则抛出异常,当i=0时表示在表头插入一个数据元素x,当i=length()时表示在表尾插入一个数据元素x
	public void insert(int i, Object x) throws Exception {
		if (curLen == listElem.length) // 判断顺序表是否已满
			throw new Exception("顺序表已满");// 输出异常

		if (i < 0 || i > curLen) // i小于0或者大于表长
			throw new Exception("插入位置不合理");// 输出异常

		for (int j = curLen; j > i; j--)
			listElem[j] = listElem[j - 1];// 插入位置及之后的元素后移

		listElem[i] = x; // 插入x
		curLen++;// 表长度增1
	}

	// 将线性表中第i个数据元素删除。其中i取值范围为:0≤i≤length()- 1,如果i值不在此范围则抛出异常
	public void remove(int i) throws Exception {
		if (i < 0 || i > curLen - 1) // i小于1或者大于表长减1
			throw new Exception("删除位置不合理");// 输出异常

		for (int j = i; j < curLen - 1; j++)
			listElem[j] = listElem[j + 1];// 被删除元素之后的元素左移

		curLen--; // 表长度减1
	}

	// 返回线性表中首次出现指定元素的索引,如果列表不包含此元素,则返回 -1
	public int indexOf(Object x) {
		int j = 0;// j为计数器
		while (j < curLen && !listElem[j].equals(x))
			// 从顺序表中的首结点开始查找,直到listElem[j]指向元素x或到达顺序表的表尾
			j++;
		if (j < curLen)// 判断j的位置是否位于表中
			return j; // 返回x元素在顺序表中的位置
		else
			return -1;// x元素不在顺序表中
	}

	// 输出线性表中的数据元素
	public void display() {
		for (int j = 0; j < curLen; j++)
			System.out.print(listElem[j] + " ");
		System.out.println();// 换行

	}

	// 实现对顺序表就地逆置
	public void reverse() {
		for (int i = 0,j=curLen-1; i < j; i++,j--) {
			Object temp = listElem[i];
			listElem[i] = listElem[j];
			listElem[j] = temp;
		}
	}

	// 实现对顺序表右移k位
	public void shit(int k) {
		int n=curLen,p=0,i,j,l;
        Object temp;
     	for(i=1;i<=k;i++)
          if(n%i==0&&k%i==0) //求n和k的最大公约数p
              p=i;
        for(i=0;i<p;i++){
           j=i;
           l=(i+n-k)%n;
           temp=listElem[i];
           while(l!=i){
             listElem[j]=listElem[l];
             j=l;
             l=(j+n-k)%n;
           }// 循环右移一步
        listElem[j]=temp;
        }
    }

}

2.3 线性表的链式存储和实现

线性表的链式存储结构就是用任意的存储单元存储数据元素,这些存储单元可以连续也可以不连续,甚至可以零散分布在内存的任意位置

逻辑上相邻的数据元素在物理位置上不一定相邻,因此,必须采用附加信息表示数据元素之间的逻辑关系。如图:Node(结点) 

 数据域用来存储数据元素,地址域用来存储它的后继所在的地址信息

①单链表

接下来我们来看一下单链表(每个结点只有一个地址域)的示意图

 next域将数据元素按逻辑次序链接在一起, ^(空)最后一个元素an-1无后继所以为^, a0无前驱所以为head头指针

头结点:在单链表最前面增加一个特殊的结点,称为头结点,如下图所示。

 

 头结点无意义只是表示从此开始

接下来我们来看一下单链表结点类定义

// 单链表结点类定义
public class Node<T> // 单链表结点类,T指定结点的元素类型
{
    public T data;     // 数据域,存储数据元素
    public Node<T> next;     // 地址域,引用后继结点
    public Node(T data,Node<T> next)     // 构造结点,data指定数据元素,next指定后继结点{
        this.data = data;     // T 对象引用赋值
        this.next = next;    //Node<T>对象引用赋值
}
    public Node(){
        this(null, null);}
    public String toString()    //返回结点数据域的描述字符串
{
        return this.data.toString();
}

是不是看起来晕晕乎乎的?没关系(因为考试上面的东西不会单独考滴),所以我们来学习一下整体的吧,来看一下带头结点的单链表及其基本操作 :

package ch02;

/**
 * 
 * 带头结点的单链表及其基本操作
 * 
 */
import java.util.Scanner;

public class LinkList implements IList {
	public Node head;// 单链表的头指针

	// 单链表的构造函数
	public LinkList() {
		head = new Node(); // 初始化头结点
	}

	public LinkList(int n, boolean Order) throws Exception {
		this();// 初始化头结点
		if (Order) // 用尾插法顺序建立单链表
			create1(n);
		else
			// 用头插法逆位序建立单链表
			create2(n);
	}

	// 用尾插法顺序建立单链表。其中n为该单链表的元素个数
	public void create1(int n) throws Exception {
		Scanner sc = new Scanner(System.in);// 构造用于输入的对象
		for (int j = 0; j < n; j++)
			// 输入n个元素的值
			insert(length(), sc.next());// 生成新结点,插入到表尾
	}

	// 用头插法逆位序建立单链表。其中n为该单链表的元素个数
	public void create2(int n) throws Exception {
		Scanner sc = new Scanner(System.in);// 构造用于输入的对象
		for (int j = 0; j < n; j++)
			// 输入n个元素的值
			insert(0, sc.next());// 生成新结点,插入到表头
	}

	// 将一个已经存在的带头结点单链表置成空表
	public void clear() {
		head.data=null;
		head.next=null;
	}

	// 判断当前带头结点的单链表是否为空
	public boolean isEmpty() {
		return head.next == null;// 判断首结点是否为空
	}

	// 求带头结点单链表中的数据元素个数并由函数返回其值
	public int length() {
		Node p = head.next;// 初始化,p指向首结点,length为计数器
		int length = 0;
		while (p != null) {// 从首结点向后查找,直到p为空
			p = p.next;// 指向后继结点
			++length;// 长度增1
		}
		return length;
	}

	// 读取带头结点单链表中的第i个数据元素
	public Object get(int i) throws Exception {
		Node p = head.next;// 初始化,p指向首结点,j为计数器
		int j = 0;
		while (p != null && j < i) {// 从首结点向后查找,直到p指向第i个元素或p为空
			p = p.next;// 指向后继结点
			++j;// 计数器的值增1
		}
		if (j > i || p == null) { // i小于0或者大于表长减1
			throw new Exception("第" + i + "个元素不存在");// 输出异常
		}
		return p.data;// 返回元素p
	}

	// 在带头结点单链表中第i个数据元素之前插入一个值为x的数据元素
	public void insert(int i, Object x) throws Exception {
		Node p = head;// 初始化p为头结点,j为计数器
		int j = -1; // 第i个结点前驱的位置
		while (p != null && j < i - 1) {// 寻找i个结点的前驱
			p = p.next;
			++j;// 计数器的值增1
		}
		if (j > i - 1 || p == null) // i不合法
			throw new Exception("插入位置不合理");// 输出异常

		Node s = new Node(x); // 生成新结点
		s.next=p.next;// 插入单链表中
		p.next=s;
	}

	// 将线性表中第i个数据元素删除。其中i取值范围为:0≤i≤length()- 1,如果i值不在此范围则抛出异常
	public void remove(int i) throws Exception {
		Node p = head;// p指向要删除结点的前驱结点
		int j = -1;
		while (p.next != null && j < i - 1) {// 寻找i个结点的前驱
			p = p.next;
			++j;// 计数器的值增1
		}
		if (j > i - 1 || p.next == null) { // i小于0或者大于表长减1
			throw new Exception("删除位置不合理");// 输出异常
		}
		p.next=p.next.next;// 删除结点
	}

	// 在带头结点的单链表中查找值为x的元素,如果找到,则函数返回该元素在线性表中的位置,否则返回-1
	public int indexOf(Object x) {
		Node p = head.next;// 初始化,p指向首结点,j为计数器
		int j = 0;
		while (p != null && !p.data.equals(x)) {// 从单链表中的首结点元素开始查找,直到p.data指向元素x或到达单链表的表尾
			p = p.next;// 指向下一个元素
			++j;// 计数器的值增1
		}
		if (p != null)// 如果p指向表中的某一元素
			return j;// 返回x元素在顺序表中的位置
		else
			return -1;// x元素不在顺序表中
	}


	// 输出线性表中的数据元素
	public void display() {
		Node node = head.next;// 取出带头结点的单链表中的首结点元素
		while (node != null) {
			System.out.print(node.data + " ");// 输出数据元素的值
			node = node.next;// 取下一个结点
		}
		System.out.println();
	}

	// 在非递减的有序单链表中插入一个值为x的数据元素,并使单链表仍保持有序的操作
	//方法一
    public void insert(int  x) {
		Node p = head.next;
		Node q = head;// q用来记录p的前驱结点
		int temp;
		while (p != null) {
			temp = ((Integer) p.data).intValue();
			if (temp < x) {
				q = p;
				p = p.next;
			} else
				break;
		}

		Node s = new Node(x); // 生成新结点
		s.next=p;// 将s结点插入到单链表的q结点与p结点之间
		q.next=s;
	}
    // 在非递减的有序单链表中插入一个值为x的数据元素,并使单链表仍保持有序的操作
	//方法二
    public void insert1(int  x) {
		Node p = head.next;

		while (p.next != null&&((Integer) p.next.data).intValue()<x) {
            p = p.next;
		}
		Node s = new Node(x); // 生成新结点
		s.next=p.next;// 将s结点插入到单链表的q结点与p结点之间
		p.next=s;
	}

    // 实现对单链表就地逆置(采用的是头插法)
	public void reverse() {
		Node p = head.next;
		head.next=null;
		Node q;
		while (p != null) {
			q = p.next;
			p.next=head.next;
			head.next=p;
			p = q;
		}
	}

	// 实现删除单链表中数据域值等于x的所有结点的操作,并返回被删除结点的个数
	public int removeAll(Object x) {
		Node p = head.next;// 初始化,p指向首结点,j为计数器
		Node q = head; // 用来记录p的前驱结点
		int j = 0;// 用来记录被删除结点的个数
		while (p != null) { // 从单链表中的首结点开始对整个链表遍历一次
			if ((p.data).equals(x)) {
				q.next=p.next;
				++j;// 计数器的值增1
			} else
				q = p;
			p = p.next;// 指向下一个元素
		}
		return j;// 返回被删除结点的个数
	}
}

接下来是不带头结点的单链表及其基本操作

package ch02;


/**
 * 
 * 不带头结点的单链表及其基本操作
 * 
 */
public class LinkList2 implements IList {

	public Node head;// 单链表的首结点指针

	// 构造函数
	public LinkList2() {
		head = null;
	}

	// 将一个已经存在的单链表置成空表
	public void clear() {
		head = null;
	}

	// 判断当前单链表是否为空
	public boolean isEmpty() {
		return head == null;
	}

	// 求单链表中的数据元素个数并由函数返回其值
	public int length() {
		Node p = head;// 初始化,p指向首结点,length为计数器
		int length = 0;
		while (p != null) {// 从首结点向后查找,直到p为空
			p = p.next;// 指向后继结点
			++length;// 长度增1
		}
		return length;
	}

	// 读取单链表中的第i个数据元素
	public Object get(int i) throws Exception {
		Node p = head;// 初始化,p指向首结点,j为计数器
		int j = 0;
		while (p != null && j < i) {// 从首结点向后查找,直到p指向第i个元素或p为空
			p = p.next;// 指向后继结点
			++j;// 计数器的值增1
		}
		if (j > i || p == null) // i小于0或者大于表长减1
			throw new Exception("第" + i + "个元素不存在");// 输出异常

		return p.data;// 返回元素p
	}

	// 在单链表中第i个数据元素之前插入一个值为x的数据元素
	public void insert(int i, Object x) throws Exception {
		Node s = new Node(x);
		if (i == 0) { // 插入位置为表头
			s.next=head;
			head = s;
			return;
		}

		Node p = head;
		int j = 0;// 第i个结点前驱的位置
		while (p != null && j < i - 1) {// 寻找i个结点的前驱
			p = p.next;
			++j;
		}
		if (j > i - 1 || p == null)
			throw new Exception("插入位置不合理");

		// 插入位置为表的中间或表尾
		s.next=p.next;
		p.next=s;
	}

	// 将线性表中第i个数据元素删除。其中i取值范围为:0≤i≤length()- 1,如果i值不在此范围则抛出异常
	public void remove(int i) throws Exception {
		Node p = head;// 初始化p为首结点,j为计数器
		Node q = null; // 用来记录p的前驱结点
		int j = 0;
		while (p != null && j < i) {// 寻找i个结点
			q = p;
			p = p.next;
			++j;// 计数器的值增1
		}
		if (j > i || p == null) // i小于0或者大于表长减1
			throw new Exception("删除位置不合理");// 输出异常

		if (q == null)
			head = null;// 删除首结点
		else
			q.next=p.next;// 删除其他结点
	}

	// 实现删除单链表中数据域值等于x的第一个结点的操作。若删除成功,则返回被删除结点的位置;否则,返回-1。
	public int remove(Object x) {
		Node p = head;// 初始化,p指向首结点
		Node q=null;  //q用来记录p的前驱结点
        int j = 0; //j为计数器
		while (p != null && !p.data.equals(x)) {// 从单链表中的首结点元素开始查找,直到p.data指向元素x或到达单链表的表尾
			 q=p;
             p = p.next;// 指向下一个元素
			 ++j;// 计数器的值增1
		 }
		if (p!=null&&q==null) //删除的是单链表中的首结点
             head=p.next;
        else if (p != null) {// 删除的是单链表中的非首结点
			   q.next=p.next;
		     }
        else
			 return -1;//值为x的结点在单链表中不存在
	    return j;
    }


	// 在不带头结点的单链表中查找值为x的元素,如果找到,则函数返回该元素在线性表中的位置,否则返回-1
	public int indexOf(Object x) {
		Node p = head;// 初始化,p指向首结点,j为计数器
		int j = 0;
		while (p != null && !p.data.equals(x)) {// 从单链表中的首结点元素开始查找,直到p.data指向元素x或到达单链表的表尾
			p = p.next;// 指向下一个元素
			++j;// 计数器的值增1
		}
		if (p != null)// 如果p指向表中的某一元素
			return j;// 返回x元素在顺序表中的位置
		else
			return -1;// x元素不在顺序表中
	}

	// 输出线性表中的数据元素
	public void display() {
		Node node = head;// 取出带头结点的单链表中的首结点元素
		while (node != null) {
			System.out.print(node.data + " ");// 输出数据元素的值
			node = node.next;// 取下一个结点
		}
		System.out.println();// 换行
	}

}

基本上把这俩看会应付考试就差不多啦~

②双链表

双链表是每个结点有两个地址域的线性链表,两个地址域分别指向前驱结点和后继结点。

 双链表的两个结构如下:

 在双链表中,知道某个结点的引用找前驱和后继都是非常容易,因为直接就有两个地址域来指向前驱和后继。

①双链表的插入操作

1:  q.prior=p.prior; 

2:p.prior.next=q;

3:  p.prior=q; 

4:  q.next=p;

注意1234的顺序不可以错, 双链表如果要插入一个元素,正向和逆向的都要插入才可以(链接结点也要更换)

②双链表的删除操作

 p.prior.next=p.next; 

p.next.prior=p.prior;

③循环单链表
将最后一个结点的next域指向head,形成环形结构,这就是循环单链表。

循环单链表与单链表操作算法基本相同只是判断条件不同:

单链表p指向尾结点时: p.next=null

循环单链表p指向尾结点时:p.next=head

循环单链表设置尾指针找开始结点和终端结点都很方便
开始结点:rear.next.next        终端结点:rear

因此,实际应用中多采用尾指针指示的循环单链表

④循环双链表

 双向循环链表及其基本操作

package ch02;

import java.util.Scanner;

/**
 * 
 * 双向循环链表及其基本操作
 * 
 */
public class DuLinkList implements IList {
	public DuLNode head;// 双向循环链表的头结点

	// 双向链表的构造函数
	public DuLinkList() {
		head = new DuLNode(); // 初始化头结点
		head.prior=head;// 初始化头结点的前驱和后继
		head.next=head;
	}

	// 从表尾到表头逆向建立双向链表的算法。其中n为该双向链表的元素个数
	public DuLinkList(int n) throws Exception {
		this();
		Scanner sc = new Scanner(System.in);// 构造用于输入的对象
		for (int j = 0; j < n; j++)
			insert(0, sc.next());// 生成新结点,插入到表头
	}

	// 在双向循环链表的第i个数据元素之前插入一个值为x的数据元素,i等于表长时,p指向头结点;i大于表长时,p=NULL。
	// 其中i取值范围为:0≤i≤length()。当i=0时表示在表头插入一个数据元素x,当i=length()时表示在表尾插入一个数据元素x
	public void insert(int i, Object x) throws Exception {
		DuLNode p = head.next;// 初始化,p指向首结点,j为计数器
		int j = 0;
		while (!p.equals(head) && j < i) {// 寻找插入位置i
			p = p.next;// 指向后继结点
			++j;// 计数器的值增1
		}

		if (j != i && !p.equals(head)) // i小于0或者大于表长
			throw new Exception("插入位置不合理");// 输出异常

		DuLNode s = new DuLNode(x);// 生成新结点
		p.prior.next=s;
		s.prior=p.prior;
		s.next=p;
		p.prior=s;
	}

	// 将双向循环链表中第i个数据元素删除。其中i 取值范围为:0≤i≤ength()-1
	public void remove(int i) throws Exception {
		DuLNode p = head.next;// 初始化,p指向首节点结点,j为计数器
		int j = 0;
		while (!p.equals(head) && j < i) {// 寻找删除位置i
			p = p.next;// 指向后继结点
			++j;// 计数器的值增1
		}

		if (j != i) // i小于0或者大于表长减1
			throw new Exception("删除位置不合理");// 输出异常

		p.prior.next=p.next;
		p.next.prior=p.prior;
	}

	// 将一个已经存在的双向循环链表置成空表
	public void clear() {
		head.prior=head;
		head.next=head;
	}

	// 判断当前双向循环链表是否为空
	public boolean isEmpty() {
		return head.equals(head.next);
	}

	// 读取双向循环链表中的第i个数据元素
	public Object get(int i) throws Exception {
		DuLNode p = head.next;// 初始化,p指向首结点,j为计数器
		int j = 0;
		while (!p.equals(head) && j < i) {// 从首结点向后查找,直到p指向第i个元素或p指向头结点
			p = p.next;// 指向后继结点
			++j;// 计数器的值增1
		}
		if (j > i || p.equals(head)) { // i小于0或者大于表长减1
			throw new Exception("第" + i + "个元素不存在");// 输出异常
		}
		return p.data;// 返回元素p
	}

	// 求双向循环链表中的数据元素个数并由函数返回其值
	public int length() {
		DuLNode p = head.next;// 初始化,p指向首结点,length为计数器
		int length = 0;
		while (!p.equals(head)) {// 从首结点向后查找,直到p指向头结点
			p = p.next;// 指向后继结点
			++length;// 长度增1
		}
		return length;
	}

	// 在双向循环链表中查找值为x的元素,如果找到,则函数返回该元素在线性表中的位置,否则返回-1
	public int indexOf(Object x) {
		DuLNode p = head.next;// 初始化,p指向首结点,j为计数器
		int j = 0;
		while (!p.equals(head) && !p.data.equals(x)) {// 从链表中的首结点元素开始查找,直到p.data指向元素x或到达链表的表尾
			p = p.next;// 指向下一个元素
			++j;// 计数器的值增1
		}
		if (!p.equals(head))// 如果p指向表中的某一元素
			return j;// 返回x元素在顺序表中的位置
		else
			return -1;// x元素不在顺序表中
	}

	public void display() {
		DuLNode node = head.next;// 取出带头结点的双向链表中的首结点
		while (!node.equals(head)) {
			System.out.print(node.data + " ");// 输出数据元素的值
			node = node.next;
		}
		System.out.println();
	}

	public DuLNode getHead() {
		return head;
	}

	public void setHead(DuLNode head) {
		this.head = head;
	}

}

 带头结点的循环链表及其基本操作类

package ch02;


/**
 * 
 * 带头结点的循环链表及其基本操作类
 * 
 */
public class CircleLinkList implements IList {
	public Node head;// 循环单链表的头指针

	// 循环单链表的构造函数
	public CircleLinkList() {
		head = new Node(); // 初始化头结点
		head.next=head;
	}

	// 将一个已经存在的带头结点的循环单链表置成空表
	public void clear() {
		head.next=head;
	}

	// 判断当前带头结点的循环单链表是否为空
	public boolean isEmpty() {
		return head.next.equals(head);
	}

	// 求带头结点的循环单链表中的数据元素个数并由函数返回其值
	public int length() {
		Node p = head.next;// 初始化,p指向首结点,length为计数器
		int length = 0;
		while (!p.equals(head)) {// 从首结点向后查找,直到p指向头结点
			p = p.next;// 指向后继结点
			++length;// 长度增1
		}
		return length;
	}

	// 读取带头结点的循环单链表中的第i个数据元素
	public Object get(int i) throws Exception {
		Node p = head.next;// 初始化,p指向首结点,j为计数器
		int j = 0;
		while (!p.equals(head) && j < i) {// 从首结点向后查找,直到p指向第i个元素或p指向头结点
			p = p.next;// 指向后继结点
			++j;// 计数器的值增1
		}
		if (j > i || p.equals(head)) { // i小于0或者大于表长减1
			throw new Exception("第" + i + "个元素不存在");// 输出异常
		}
		return p.data;// 返回元素p
	}

	// 在带头结点的循环单链表中第i个数据元素之前插入一个值为x的数据元素
	public void insert(int i, Object x) throws Exception {
		Node p = head;// 初始化p为头结点,j为计数器
		int j = -1; // 第i个结点前驱的位置
		while ((!p.equals(head) || j == -1) && j < i - 1) {// 寻找i个结点的前驱
			p = p.next;
			++j;// 计数器的值增1
		}
		if (j > i - 1 || (p.equals(head) && j != -1)) // i不合法
			throw new Exception("插入位置不合理");// 输出异常

		Node s = new Node(x); // 生成新结点
		s.next=p.next;// 插入单链表中
		p.next=s;
	}

	// 将循环单链表中第i个数据元素删除。其中i取值范围为:0≤i≤length()- 1,如果i值不在此范围则抛出异常
	public void remove(int i) throws Exception {
		Node p = head;// p指向要删除结点的前驱结点
		int j = -1;
		while ((!p.next.equals(head) || j == -1) && j < i - 1) {// 寻找i个结点的前驱
			p = p.next;
			++j;// 计数器的值增1
		}
		if (j > i - 1 || (p.next).equals(head)) { // i小于0或者大于表长减1
			throw new Exception("删除位置不合理");// 输出异常
		}
		p.next=p.next.next;// 删除结点
	}

	// 在带头结点的循环单链表中查找值为x的元素,如果找到,则函数返回该元素在线性表中的位置,否则返回-1
	public int indexOf(Object x) {
		Node p = head.next;// 初始化,p指向首结点,j为计数器
		int j = 0;
		while (!p.equals(head) && !p.data.equals(x)) {// 从单链表中的首结点元素开始查找,直到p.data指向元素x或到达单链表的表尾
			p = p.next;// 指向下一个元素
			++j;// 计数器的值增1
		}
		if (!p.equals(head))// 如果p指向表中的某一元素
			return j;// 返回x元素在顺序表中的位置
		else
			return -1;// x元素不在顺序表中
	}

	// 输出循环链表中的数据元素
	public void display() {
		Node node = head.next;// 取出带头结点的单链表中的首结点元素
		while (!node.equals(head)) {
			System.out.print(node.data + " ");// 输出数据元素的值
			node = node.next;// 取下一个结点
		}
		System.out.println();// 换行
	}


}

2.4 顺序表和链表的比较

①时间性能比较

随机访问操作:
顺序表
对于按位置随机访问的操作,时间性能为O(1)            
链表按位置访问只能从头开始依次向后扫描,所需的平均时间为O(n)

插入删除操作:
链表不需要移动数据元素,插入或删除后继结点所需的时间为O(1)
顺序表需要移动数据元素,时间性能为O(n)

(如果删除后继结点用链表方便些)

一般规律:

若线性表需要频繁进行查找却很少进行插入或删除操作,或者操作和数据元素的位序密切相关时,宜采用顺序表作为存储结构。
若线性表需要频繁进行插入和删除操作,则宜采用链表作为存储结构

②空间性能比较

结点存储密度:
顺序表
只需存储数据元素引用,存储空间利用率较高。
链表除了存储数据元素引用变量外,还要存储元素之间逻辑关系的引用变量,引用的结构性开销占了整个结点的一半。

存储空间分配:
顺序表
需要分配一定长度的存储空间,分配过大,存储空间得不到充分利用,造成浪费,分配过小,则会产生溢出;
链表不需要固定长度的存储空间,对链表中的元素个数没有限制。

一般规律:
当线性表中元素个数变化较大或未知时,最好采用链表实现。

如果事先知道线性表的大致长度,使用顺序表的空间利用率会更高。

2.5 例题

1.判断题

1-1 对于顺序存储的长度为N的线性表,删除第一个元素和插入最后一个元素的时间复杂度分别对应为O(1)和 O(N)。F

解析:因为是顺序存储,所以删除第一个元素后所有元素都需向前移动一位

1-2 在线性表的顺序存储结构中,插入和删除元素时,移动元素的个数与该元素的位置有关。  T

1-3 顺序存储的线性表可以随机存取。  T

1-4 顺序表 - 存储结构

顺序表中逻辑上相邻的元素,其物理位置也一定相邻。  T

1-5 线性表采用链式存储结构时,各个数据元素的存储单元地址一定是不连续的。 F

解析:连续与否均可 

1-6 线性表采用链表存储时,结点和节点内部的存储空间可以是不连续的。 F

 解析:内部的存储空间是连续的,节点之间可以是不连续的

1-7 对单链表来说,只有从头结点开始才能访问到表中所有结点。 (1分) T

2.单选题

2-5 在一个长度为n的顺序表中,删除第i个元素(1≤i≤n)时需要移动( )个元素。

A. n-i B. n-i+1 C. n-i-1 D. i  

2-6 在下列对顺序表进行的操作中,算法时间复杂度为O(1)的是( A  )

 A. 访问第i个元素的前驱 B.在第i个元素之后插入一个新元素(1<=i<=n ) C. 删除第i个元素(1<=i<=n ) D. 对顺序表中元素进行排序

2-7 将两个各有n个元素的递增有序顺序表归并成一个有序顺序表,其最少的比较次数是( A

 A. n B. 2n-1 C. 2n D. n-1

2-8 在单链表中,将 s 所指新结点插入到 p 所指结点之后,其语句应该为 ▁▁▁▁▁ 。

A. p.next = s; s.next = p.next; B. s.next= p.next; p.next = s.next; C. s.next = p.next; p.next = s; D. p.next = s.next; s.next = p.next;

2-9 对于一个头指针为p的带头结点的单循环链表,判定该表为空表的条件是 __C__ 。

A. p.next==NULL B. p==NULL C. p.next==p D. p!=NULL

(2-8/9不会的宝子返回看一下单链表和循环链表哦)

2-10 在双链表中,删除 s 所指代结点,其语句应该为 ▁▁▁A▁▁。

A. s.next.prior = s.prior; s.prior.next = s.next; B. s.next = s.next.next; s.next.prior = s; C. s.prior.next = s; s.prior = s.prior.prior; D. s.prior = s.next.next; s.next = s.prior.prior;

2-11 在双链表中,删除 p 所指代结点的后继结点,其语句应该为 ▁▁C▁▁▁ 。

A. s = p; s.next.prior = p; p.next = s.next; B. s = p; s.next.prior = p.prior; p.next = s.next; C. s = p.next; s.next.prior= p; p.next = s.next; D. s = p.next; s.next.prior = p; p.next = s.next.next

(2-10/11不会的宝子们可以返回再看一下双链表的知识,静下心来慢慢分析一下哦,可能这种题第一次做很慢,但是多做慢慢就可以快速理解啦,熟能生巧哦)

填空题 

4-1 数组A[1..5,1..6]每个元素占5个单元,将其按行优先次序存储在起始地址为1000的连续的内存单元中,则元素A[5,6]的地址为: 1145 

解析:1000+5*6*5-5=1145    起始地址为1000,[1,1] [1,2]……[5,6]一共为5*6个元素,每个元素占5单元,所以一共占5*6*5,再加上起始地址,就是总的,题干问[5,6]的地址,此时这个位置还未存储,所以减五,即1000+5*6*5-5=1145

4-2 在有n个元素的顺序表中的任意位置插入一个元素所需移动元素的平均次数为 n/2

解析:n个元素的顺序表可插入的位置有n+1个,移动次数总数为0+1+2…+n=(0+n)*(n+1)/2,平均次数为 总数/(n+1)= n/2

2.6 写在最后

①搭配课后习题巩固学习效果会更好哦~(课后习题更新中)

②该系列数据结构使用语言为Java,但是数据结构本身是一种逻辑上的概念,它是独立于特定语言的,所以学习数据结构不必拘泥于某种特定语言,每种语言都可以实现特定的数据结构,差别只在于语法实现级别。

③该系列数据结构知识点内容参照 中国大学mooc 徐爱琴老师的数据结构java(链接在第一章)d29101a997c842fdaead9894659bde4a.jpeg

④ 该系列数据结构代码案例与课后习题参照书本 《数据结构——Java语言描述》f45225cfc07d45e58245adc22cc08f4c.jpeg

该系列数据结构为梨子(我)在学习数据结构时总结出的知识点,例题为自己做题时觉得还不错的题目,习题为书本课后习题。如有任何错误希望大家可以多多指正哈~如果觉得我的文章写的还不错,可以给个三连!!!我也会按照自己的学习进度持续更新的哦~

哦对,这一章的学习可能对部分同学来说有些难度,看的云里雾里的,没关系哈,梨子刚学的时候也很懵,静下心来多看看多理解,如果实在不会的话就多做做习题,从做题中找感觉,慢慢理解了。

最后希望大家都能通过数据结构这门考试并且有所收获,我们一起加油吧!

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

梨子想当程序猿

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

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

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

打赏作者

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

抵扣说明:

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

余额充值