4.线性表

定义

由零个或多个数据元素组成的有限序列。

  • 首先,他是一个序列,也就是元素之间是有个先来后到的关系。
  • 若元素存在多个,则第一个元素无前驱,而最后一个元素无后继,其它元素都有且只有一个前驱和后继。
  • 线性表的强调是有限的。

例子:

若线性表记为(a1,a2,…,ai-1,ai,ai+1,…,an),则表中ai-1领先于ai,ai领先于ai+1,称ai-1是ai的直接前驱元素,ai+1是ai的直接后驱元素。
在这里插入图片描述
所以线性表元素的个数n(n>=0)定义为线性表的长度,当n=0时,称为空表。

4.1 线性表的抽象数据类型
ADT List{
	数据对象:D = {ai 属于某个数据类型, i=0,1,2,3,4...}
		    D = {a0,a1,a2,a3,a4,...,an}  #所有元素都是同一个数据类型
	数据关系:R = {<ai,ai+1} 
	数据操作:
		getSize();      #返回线性表中元素的个数
		isEmpty();      #判断线性表是否为空,为空返回true,否则返回false
		insert(i,e);    #在线性表的i索引值位置,插入元素e,如果索引值i越界,报错
		contains(e);    #在线性表中判断是否存在元素e,存在返回true
		indexOf(e);     #返回元素e在线性表中的索引值,如果不存在元素e,返回-1
		remove(e);      #删除线性表中第一个与e相同的元素,删除成功返回删除的元素
		remove(i);      #删除线性表指定索引值的元素,如果索引值i越界,报错
		replace(i,e);   #把线性表中索引值为i的元素替换为元素e,如果索引值i越界,报错
		get(i);         #返回线性表中索引值为i的元素,如果索引值i越界,报错
		insertBefore(p,e);#在线性表元素p的前面插入元素e
		insertAfter(p,e);#在线性表元素p的后面插入元素e		
}List;

抽象数据类型可以对应的一个java类,数据对象与元素之间的关系可以通过成员变量来存储和表示,数据操作可以通过一组方法来实现。

4.2 List接口

使用java中的接口来表示ADT的数据操作,在使用类完成抽象数据类型时,只要这个类实现接口就可以完成抽象数据类型中定义的操作。

/**
 * 通过接口定义一组线性表中的操作
 * @author 薛鑫泰
 *
 */
public interface MyList {
	int getSize();   //返回线性表中元素的个数
	boolean inEmpty(); //判断线性表是否为空
	void insert(int i, Object e); //在线性表的i索引值出添加元素e
	boolean contains(Object e);  //判断线性表是否包含元素e
	int indexOf(Object e);  //返回线性表中元素e的索引值
	Object remove(Object e);  //删除线性表中第一个与e相同的元素,并返回该元素
	Object remove(int i);    //删除线性表中索引值为i的元素,并返回该元素
	Object replace(int i, Object e); //使用元素e替换线性表中i索引值处的元素,并返回旧元素
	Object get(int i);  //返回索引值为i的元素
	boolean insertBefore(Object p, Object e);  //在线性表中元素p的前面插入元素e
	boolean insertAfter(Object p, Object e);  //在线性表中元素p的后面插入元素e
}
4.3 线性表的顺序存储与实现

线性表的顺序存储就是使用一组地址连续的存储空间来依次存储线性表中的元素,表中的元素以数据类型在计算机内存的地址相邻性表示数据元素之间的关系。

在java中可以使用数据来存储线性表中的数据元素,

4.3.1 插入元素

在这里插入图片描述

4.3.2 删除操作

在这里插入图片描述

4.3.3 具体代码实现

实现接口,并编写代码

/**
 * 通过数组实现线性表
 * @author 薛鑫泰
 *
 */
public class MyArrayList implements MyList{
	
	private Object[] elements;  //定义数组保存数据元素
	private static final int DEFAULT_SIZE = 16;  //定义数组的默认初始化容量
	private int size;  //保存数据元素个数
	
	//构造方法,用来初始化数组
	public MyArrayList() {
		elements = new Object[DEFAULT_SIZE];
	}
	public MyArrayList(int initialSize) {
		elements = new Object[initialSize];
	}
    
	//返回元素个数
	@Override
	public int getSize() {
		return size;
	}

	//判断线性表是否为空
	@Override
	public boolean isEmpty() {
		return size == 0;  //如果size等于0返回true,否则返回false
	}

	//在线性表中插入元素e
	@Override
	public void insert(int i, Object e) {
		//判断索引值i是否越界
		if(i < 0 || i > size) {
			throw new IndexOutOfBoundsException(i+"越界");
		}
		//如果数组已满,对数组进行扩容
		if(size >= elements.length) {
			expandSpace(); //对数组扩容
		}
		//从i开始,元素依次后移
		for(int j = size; j > i; j--) {    
			elements[j] = elements[j-1];   //将j-1的值赋值给j,从而实现元素后移
		}
		//把元素e存储到i位置
		elements[i] = e;
		//因为插入了新元素,元素的个数增1
		size++;
	}
    
	//数组扩容
	private void expandSpace() {
		//定义一个更大的数组,默认按2倍大小进行扩容
		Object[] newElements = new Object[elements.length * 2];
		//把原来的数据内复制给新的数组
		for(int i = 0; i < elements.length; i++) {
			newElements[i] = elements[i];
		}
		//让原来的数组名指向新的数组
		elements = newElements;
		
	}
    
	//判断当前线性表是否包含元素e
	@Override
	public boolean contains(Object e) {
		
		return indexOf(e) >= 0;  // 判断e在线性表的索引值是否大于零,是存在,否则不存在
	}
    
	// 返回元素e在线性表中的第一次出现的索引值,如果不存在返回-1
	@Override
	public int indexOf(Object e) {
		//遍历数组
		if(e == null) {
			//线性表中,用户可能添加了null
			for(int i = 0; i < size; i++) {
				if(elements[i] == null) {
					return i;
				}
			}
		}else {
			for(int i = 0; i < size; i++) {
				if(e.equals(elements[i])) {
					return i;
				}
			}
		}
		return -1;
	}
    
	//在线性表中,删除第一个与e相同的元素
	@Override
	public Object remove(Object e) {
		//获得e在线性表中的索引值
		int index = indexOf(e);
		if(index < 0) {
			return null;  //线性表中不存在元素e
		}
		return remove(index);  //调用删除指定索引值的元素的方法
	}
    
	//删除指定索引值的元素
	@Override
	public Object remove(int i) {
		//判断i是否越界
		if(i < 0 || i >= size) {
			throw new IndexOutOfBoundsException(i+"越界");
		}
		//将要删除的元素保存
		Object old = elements[i];
		//把i+1开始的元素依次前移
		for(int j = i; j < size-1; j++ ) {  //从第i个元素开始,后面每个元素覆盖之前的元素
			elements[j] = elements[j+1];
		}
		//把最后的元素置为null
		elements[size-1] = null;
		//修改元素的个数
		size--;
		//返回删除的元素
		return old;
	}
	
	//把索引值为i的元素替换为e
	@Override
	public Object replace(int i, Object e) {
		//判断i是否越界
		if(i < 0 || i >= size) {
			throw new IndexOutOfBoundsException(i+"越界");
		}
		//替换
		Object old = elements[i];
		elements[i] = e;
		//返会被替换的值
		return old;
	}

    //获取指定索引值的元素
	@Override
	public Object get(int i) {
		//判断i是否越界
		if(i < 0 || i >= size) {
			throw new IndexOutOfBoundsException(i+"越界");
		}
		return elements[i];
	}

	//在指定的元素前查入元素
	@Override
	public boolean insertBefore(Object p, Object e) {
		//获取p元素的索引值
		int index = indexOf(p);
		if(index < 0) {
			return false;  //元素不存在,不能插入
		}
		//插入元素
		insert(index, e);
		return true;
	}
    
	//在指定的元素后查入元素
	@Override
	public boolean insertAfter(Object p, Object e) {
		//获取元素p的索引值
		int index = indexOf(p);
		if(index < 0) {
			return false;
		}
		//插入元素
		insert(index+1, e);
		return true;
	}
    
	//重写toString方法
	@Override
	public String toString() {
		//把线性表的每个元素连接起来,遍历数组中已添加的元素
		StringBuilder sb = new StringBuilder();
		sb.append("[");
		for(int i = 0; i < size; i++) {
			sb.append(elements[i]);
			//数据之间使用,分割
			if(i < size - 1) {    //如果i不是最后一个元素,添加一个,
				sb.append(",");  
			}
		}
		sb.append("]");
		return sb.toString();
	}
}

测试

public class MyTest {
	public static void main(String[] args) {
		//创建一个MyArrayList对象
		MyArrayList list = new MyArrayList();
		
		//判断线性表是否为空,和获取大小
		System.out.println(list.isEmpty());  //true
		System.out.println(list.getSize());  //0
		
		//添加元素
		list.insert(0, "bb");
		list.insert(1, "cc");
		list.insert(0, "aa");
		
		System.out.println(list.isEmpty());  //false
		System.out.println(list.getSize());  //3
		
		//把线性表中的内容打印
		System.out.println(list);  //[aa,bb,cc]
		
		//输出元素在线性表中的索引值
		System.out.println(list.indexOf("aa"));  //0
		System.out.println(list.indexOf("bb"));  //1
		System.out.println(list.indexOf("cc"));  //2
		System.out.println(list.indexOf("dd"));  //-1
		//判断线性表是否包含元素
		System.out.println(list.indexOf("aa"));  //true
		System.out.println(list.contains("dd")); //false
		
		//删除元素
		list.remove("dd");
		System.out.println(list);  //[aa,bb,cc]
		list.remove("aa");
		System.out.println(list);  //[bb,cc]
		list.remove(1);
		System.out.println(list);  //[bb]
		
		//替換
		list.insert(0, "xx");
		list.insert(0, "oo");
		System.out.println(list);  //[oo,xx,bb]
		list.replace(2, "BB");
		System.out.println(list);  //[oo,xx,BB]
		
		//获取指定索引值的元素
		System.out.println(list.get(0));  //oo
		System.out.println(list.get(1));  //xx
		System.out.println(list.get(2));  //BB
		//越界
		//System.out.println(list.get(33)); //java.lang.IndexOutOfBoundsException: 33越界
		
		//插入元素
		list.insertBefore("oo", "JJ");
		System.out.println(list);        //[JJ,oo,xx,BB]
		list.insertAfter("oo","jj");
		System.out.println(list);        //[JJ,oo,jj,xx,BB]
	}
}
4.3.4 顺序存储的特点

优点:

  • 顺序存储时使用数组实现的,数组可以通过索引值快速访问每个元素
    在这里插入图片描述

  • 为什么通过下标可以访问数组元素?

    通过下标可以计算数组元素的地址;

    data[2]元素的地址计算方式:data + 2 * 4

    data是数组名,保存数组的起始位置

    2是下标

    4是数组元素类型所占的字节数,数组中存储的是int类型,每个元素占4个字节,所以 data + 2 * 4 = 0x1234 + 2 * 4

存储器中每个存储单元都有自己的编号,这个编号称为地址。
公式为:*LOC(ai)=LOC(a1)+(i-1)c
LOC为获得存储位置的函数。
c表示c个存储单元

例子:

对于线性表的顺序存储结构,设起始地址为66,每个元素占5个存储单元,求第12个元素的内容存储在哪几个存储单元中。

第12个元素的起始地址为:66+5*(12-1) = 121

缺点:

  • 在插入/删除元素时,需要移动大量的元素。
  • 当线性表长度变化较大时,很难确定存储空间的容量。

应用场景:

  • 适合存储的元素,插入/操作比较少,主要时查询操作。
4.4 线性表的链式存储与实现

链表:

一种重要的数据结构,HashMap等集合的底层结构都是链表结构。链表以结点作为存储单元,这些存储单元可以是不连续的。每个结点由两部分组成:存储的数值+前序结点和后序结点的指针。即有前序结点的指针又有后序结点的指针的链表称为双向链表,只包含后续指针的链表为单向链表。

4.4.1 单向链表

单向链表,即单链表。每个存储单元至少包含两个存储域,一个用来存储数据,一个保存下个存储单元的引用。

各个存储单元的地址可以是不连续的。
在这里插入图片描述

插入和删除分析
在这里插入图片描述

如果插入的是在0位置,那么刚插入的结点就是头结点。

需要新建一个结点来存储刚插入的结点:newNode

  • newNode.next = head; //将新的头结点的next指向原来的头结点
  • head = newNode; //将刚创建的结点,改为头结点
4.4.2 线性表的链式存储结构代码实现
public class MySingleLink implements MyList {
    private Node head;  //头结点
    private int size;   //保存元素的个数

    //返回元素的个数
    @Override
    public int getSize() {
        return size;
    }
    
    //判断线性表是否为空
    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    //在线性表中插入元素
    @Override
    public void insert(int i, Object e) {
        //判断索引值i是否越界
        if(i < 0 || i > size) {
            throw new IndexOutOfBoundsException(i+"越界");
        }
        //创建结点,因为插入元素需要创建新的结点
        Node newNode = new Node(e,null);
        //头结点为null的情况,链表不存在,刚刚添加的结点就是头结点
        if(head == null) {
            head = newNode;
        }else {
            //在0位置插入结点
            if(i == 0) {
                newNode.next = head;   //将新的头结点的next指向原来的头结点
                head = newNode;   //将刚创建的结点,改为头结点
            }else {
                //插入结点,先找到i-1结点
                Node pNode = head;  //根据头结点来找i-1结点
                for(int j = 1;j < i; j++) {
                    pNode = pNode.next;  //循环结束,将i-1这个结点的next赋值
                }
                //注意,先修改新结点的next指针域,再修改i-1结点的指针域指向新结点
                newNode.next = pNode.next;
                pNode.next = newNode;
            }
        }
        size++;
    }

    //判断线性表中是否包含元素
    @Override
    public boolean contains(Object e) {
        return indexOf(e) >= 0;
    }

    //返回元素在线性表中第一次出现的索引值
    @Override
    public int indexOf(Object e) {
        int i = 0;  //保存元素e的索引值
        Node pNode = head;
        while(pNode != null) {
            if(e == null && pNode.data == null) {
                return i;
            }else if(e != null && e.equals(pNode.data)){
                return i;
            }
            i++;
            pNode = pNode.next;
        }
        return -1;
    }

    //从线性表中删除第一个与e相同的元素
    @Override
    public Object remove(Object e) {
        //找到元素e第一次出现的索引值
        int index = indexOf(e);
        if(index < 0) {
            return null;  //元素不存在
        }
        return remove(index);
    }

    //从线性表中删除指定索引值的元素
    @Override
    public Object remove(int i) {
        //判断是否越界
        if(i < 0 || i >= size) {
            throw new IndexOutOfBoundsException(i+"越界");
        }
        Node pNode = head;
        //删除头结点
        if(i == 0) {
            head = pNode.next;
            size--;
            return pNode.data; //返回删除头结点的元素
        }
        //找到i-1结点
        for(int j = 1; j < i; j++) {
            pNode = pNode.next;
        }
        Object old = pNode.data;
        pNode.next = pNode.next.next;  //修改i-1结点的next指针域,指向i+1结点
        size--;
        return old;
    }

    //把线性表中索引值为i的元素替换为e
    @Override
    public Object replace(int i, Object e) {
        //判断是否越界
        if(i < 0 || i >= size) {
            throw new IndexOutOfBoundsException(i+"越界");
        }
        //找到i结点
        Node pNode = getNode(i);

        Object old = pNode.data;
        pNode.data = e;
        return old;
    }

    //返回索引值为i的元素
    @Override
    public Object get(int i) {
        //判断是否越界
        if(i < 0 || i >= size) {
            throw new IndexOutOfBoundsException(i+"越界");
        }
        Node pNode = getNode(i);
        return pNode.data;
    }

    //定义一个方法,返回索引值为i的元素
    private Node getNode(int i) {
        //是否越界
        if(i < 0 || i >= size) {
            return null;
        }
        //头结点
        if(i == 0) {
            return head;
        }
        Node pNode = head;
        for(int j= 0; j < i; j++) {
            pNode = pNode.next;
        }
        return pNode;
    }

    //在指定的元素p前面插入元素
    @Override
    public boolean insertBefore(Object p, Object e) {
        int index = indexOf(p);
        if(index < 0) {
            return false; //元素p不存在
        }
        insert(index, e);
        return true;
    }

    //在指定的元素p后面插入元素
    @Override
    public boolean insertAfter(Object p, Object e) {
        int index = indexOf(p);
        System.out.println("-->"+get(index+1));
        if(index < 0) {
            return false; //元素p不存在
        }
        insert(index+1, e);
        return true;
    }

    //重写toString()方法
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        Node pNode = head;
        while(pNode != null) {
            sb.append(pNode.data);
            //使用逗号分割
            if(pNode.next != null) {
                sb.append(",");
            }
            pNode = pNode.next;  //指针下移
        }
        sb.append("]");
        return sb.toString();
    }
    //定义一个内部类表示单向链表中的结点
    private class Node{
        Object data;  //保存数据
        Node next;    //下个结点的引用
        public Node(Object data, Node next) {
            this.data = data;
            this.next = next;
        }
    }
}

代码测试

public class MySingleTest {
    public static void main(String[] args) {
        //创建链表
        MySingleLink link = new MySingleLink();

        //判断链表是否为空和获取大小
        System.out.println(link.isEmpty());  //true
        System.out.println(link.getSize());  //0

        //插入元素
        link.insert(0, "aa");
        link.insert(0, "bb");
        link.insert(0, "cc");

        System.out.println(link);  //[cc,bb,aa]
        System.out.println(link.isEmpty());  //false
        System.out.println(link.getSize());  //3

        //判断元素是否存在
        System.out.println(link.contains("aa"));  //true
        System.out.println(link.indexOf("bb"));   //1
        System.out.println(link.indexOf("xx"));   //-1

        //删除结点
        System.out.println(link.remove("xxx"));  //null
        System.out.println(link.remove("cc"));   //cc
        System.out.println(link);                //[bb,aa]
        System.out.println(link.getSize());
        System.out.println(link.remove("bb"));   //bb
        System.out.println(link);               //[aa]
        //获取指定索引值的元素
        System.out.println(link.get(0));  //aa
        //替换元素
        link.replace(0, "ww");
        System.out.println(link);  //[ww]
        System.out.println(link.insertBefore("ww", "BB"));
        System.out.println(link);
        link.insertAfter("BB","ss");
        System.out.println(link);
    }
}
4.4.3 双向链表

单向链表只能通过一个结点的引用访问它的后继结点,不能访问前驱结点,如果要找某个结点的前驱结点,需要从头开始依次查找。

在双向链表中,扩展了结点的结构,每个结点除了存储数据外,通过一个引用指向后续结点,再定义一个引用指向前驱结点。
在这里插入图片描述

双向链表的结构:
在这里插入图片描述

插入和删除图解
在这里插入图片描述

4.4.4 通过代码实现双向链表
public class MyDualLinkedList implements MyList {

    private Node first;  //指向头结点
    private Node last;  //指向尾结点
    private int size;  //用来保存元素的个数

    //返回元素的个数
    @Override
    public int getSize() {
        return size;
    }

    //判断元素是否为空
    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    //在指定索引值的位置插入元素
    @Override
    public void insert(int i, Object e) {
        //判断是否越界
        if(i < 0 || i > size){
            throw new IndexOutOfBoundsException(i+"越界");
        }
        //如果i==0,在头部插入元素
        if(i == 0){
            addFirst(e);
        }else if(i == size){
            //如果i==size,在尾部插入元素
            addLast(e);
        }else{
            //找到i结点,再i结点前插入元素
            Node pNode = getNode(i);  //i索引值的元素
            Node prevNode = pNode.prev; //i-1索引值的元素
            //生成新结点
            Node newNode = new Node(prevNode,e,pNode);
            //修改前驱结点的后继
            prevNode.next = newNode;
            //修改后继结点的前驱
            pNode.prev = newNode;
            size++;   //插入元素,链表大小+1
        }
   }

   //返回索引值对应的结点
    private Node getNode(int i) {
        //从头结点开始遍历查找i索引值对应的结点
        Node pNode = first;
        for(int j = 0; j < i; j++){
            pNode = pNode.next;
        }
        return pNode;
    }

    /**
     * 在链表中经常会对头元素和尾元素进行操作
     */
    //在尾部添加元素
    public void addLast(Object e) {
        Node pNode = last;
        //生成一个新结点
        Node newNode = new Node(last,e,null);
        if(pNode == null){  //尾结点为空,表示链表为空
             first = newNode;
        }else{
            pNode.next = newNode;
        }
        last = newNode;
        size++;
    }

    //在头部添加元素
    public void addFirst(Object e) {
        Node pNode = first;  //将头结点赋值给pNode
        //定义一个新结点
        Node newNode = new Node(null,e,first);
        first = newNode;    //将新结点变为头结点
        if(pNode == null){  //如果链表为空的时候,新结点即是头结点,也是尾结点
            last = newNode;
        }else{
            pNode.prev = newNode;  //将prev指向头结点
        }
        size++;  //元素个数加一
    }

    //判断链表中是否包含元素e
    @Override
    public boolean contains(Object e) {
        return indexOf(e) >= 0;  //大于0表示存在,否则不存在
    }

    //判断元素e在链表中第一次出现的位置,如果不存在返回-1
    @Override
    public int indexOf(Object e) {
        int i = 0;  //保存元素e在链表中的索引值
        //依次遍历链表中的各个结点,比较链表中的元素是否和e一样
        if(e == null){
            for(Node pNode = first; pNode != null;pNode = pNode.next){
                if(pNode.data == null){
                    return i;
                }
                i++;
            }
        }else{
            for(Node pNode = first; pNode != null;pNode = pNode.next){
                if(e.equals(pNode.data)){
                    return i;
                }
                i++;
            }
        }
        return -1;
    }

    @Override
    public Object remove(Object e) {
        //找到元素e对应的索引值
        int index = indexOf(e);
        if(index < 0){  //链表中没有该元素
            return null;
        }
        return remove(index);
    }

    @Override
    public Object remove(int i) {
        if(i < 0 || i >=size){
            throw new IndexOutOfBoundsException(i+"越界");
        }
        Node pNode = getNode(i);  //获取i索引值位置的元素
        Node prevNode = pNode.prev;  //删除元素的前驱元素
        Node nextNode = pNode.next;  //删除元素的后继元素
        if(prevNode == null){  //表示删除的是头结点
            first = nextNode;
        }else{
            prevNode.next = nextNode;  //删除元素的前驱元素指向删除元素的后继元素
        }
        if(nextNode == null){  //表示删除的尾结点
            last = prevNode;
        }else{
            nextNode.prev = prevNode;  //删除元素的后继元素指向删除元素的前驱元素
        }
        size--;  //链表大小-1
        return pNode.data;  //返回删除元素的值
    }

    //替换指定索引值的元素,把原来的元素返回
    @Override
    public Object replace(int i, Object e) {
        if(i < 0 || i >=size){
            throw new IndexOutOfBoundsException(i+"越界");
        }
        //找到索引值为i的结点
        Node pNode = getNode(i);
        Object old = pNode.data;
        pNode.data = e;
        return old;
    }

    //返回指定索引值的元素
    @Override
    public Object get(int i) {
        if(i < 0 || i >=size){
            throw new IndexOutOfBoundsException(i+"越界");
        }
        Node pNode = getNode(i);
        return pNode.data;
    }

    //在指定的元素p前面插入元素e
    @Override
    public boolean insertBefore(Object p, Object e) {
        //找到p元素在链表中的位置
        int index = indexOf(p);
        if(index < 0){  //链表中不存在元素e
            return false;
        }
        insert(index,e);     //在p元素的前面插入元素e
        return true;
    }

    //在指定的元素p后面插入元素e
    @Override
    public boolean insertAfter(Object p, Object e) {
        //找到p元素在链表中的位置
        int index = indexOf(p);
        if(index < 0){  //链表中不存在元素e
            return false;
        }
        insert(index+1,e);     //在p元素的后面插入元素e
        return true;
    }

    //删除第一个元素,删除头元素
    public Object removeFirst(){
        return remove(0);
    }
    //删除最后一个元素(尾结点)
    public Object removeLast(){
        return remove(size-1);
    }

    //返回头元素
    public Object getFirst(){
        return get(0);
    }
    //返回尾元素
    public Object getLast(){
        return get(size -1);
    }

    //重写toString方法
    @Override
    public String toString(){
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for(Node node = first; node != null; node = node.next){
            sb.append(node.data);
            if(node != last){
                sb.append(",");
            }
        }
        sb.append("]");
        return sb.toString();
    }

    //定义一个内部类,描述双向链表
    private class Node{
        Object data;
        Node prev;
        Node next;

        public Node( Node prev, Object data,Node next) {
            this.prev = prev;
            this.data = data;
            this.next = next;
        }
    }
}

代码测试

/**
 * 测试双向链表
 */
public class Test {
    public static void main(String[] args) {
        //创建双向链表
        MyDualLinkedList list = new MyDualLinkedList();

        //获取大小和判断是否为空
        System.out.println(list.getSize());  // 0
        System.out.println(list.isEmpty());  // true

        //处插入数据
        list.insert(0,"aa");
        list.insert(0,"bb");
        list.insert(1,"cc");
        System.out.println(list);   // [bb,cc,aa]

        //判断是否包含元素
        System.out.println(list.indexOf("cc"));  // 1
        System.out.println(list.contains("bb")); //true
        System.out.println(list.indexOf("xx"));  //-1

        //删除指定的结点
       // System.out.println(list.remove(2));  // aa
        //System.out.println(list.remove("bb")); //bb
        //System.out.println(list);  //[cc]

        //替换元素
        System.out.println(list.replace(0,"AA"));  //bb
        System.out.println(list);  // [AA,cc,aa]
        System.out.println(list.get(2));  //aa

        //在前面插入元素
        System.out.println(list.insertBefore("aa","bb"));
        System.out.println(list.insertAfter("AA","dd"));
        System.out.println(list);

        //在头部添加
        list.addFirst("mm");
        //在尾部添加
        list.addLast("MM");
        System.out.println(list);  //[mm,AA,dd,cc,bb,aa,MM]
        //获取头元素的值
        System.out.println(list.getFirst());  //mm
        //获取尾元素的值
        System.out.println(list.getLast());   //MM

        //删除头元素
        System.out.println(list.removeFirst());  //mm
        //删除尾元素
        System.out.println(list.removeLast());  //MM
        System.out.println(list);   //[AA,dd,cc,bb,aa]
    }
}
4.4.5 顺序存储和链式存储实现线性表的比较> 时间上的比较

线性表的基本操作:查询、插入、删除

  • 查询:

    • 数组顺序存储,直接通过索引值访问每个元素,实现了数组元素的随机访问
    • 链式存储,每次从头结点或尾结点开始依次查找

    如果线性表主要是查询操作,优先选择顺序存储的线性表

  • 插入与删除

    • 数组顺序存储实现的线性表,在插入/删除时,需要移动大量的元素
    • 链式存储,只需要修改结点的前驱后继指针即可,不需要移动元素

    如果线性表经常用于插入/删除操作,优先选择链式存储实现的线性表

空间比较

  • 顺序存储,需要预先分配一块连续的存储空间,在使用过程中会出现闲置的空间
  • 链式存储的空间是动态分配的,不会浪费空间。

如果线性表的长度经常变化,优先选择链式存储。

如果线性表的长度变化不大时,优先选择顺序存储,因为链式存储需要额外的空间存储它前驱和后继。

好的,我会尽力回答你的问题。针对你的问题,我来分别介绍一下顺序存储结构和链式存储结构下的线性表基本操作的实现。 1. 顺序存储结构下线性表基本操作的实现 顺序存储结构是指将线性表中的元素按照逻辑顺序依次存放在一块连续的存储区域中,这个存储区域可以是数组或者是一段连续的内存空间。在顺序存储结构下,线性表的基本操作主要包括插入和删除两种操作。 插入操作的基本思路如下: 1. 判断线性表是否已满; 2. 将第i个元素之后的所有元素依次后移一个位置,以便腾空一个位置; 3. 将新元素插入到第i个位置。 具体实现可以参考下面的代码: ```c++ #define MAXSIZE 100 // 定义线性表最大长度 typedef struct { int data[MAXSIZE]; // 用数组存储线性表的元素 int length; // 记录线性表的长度 } SqList; bool InsertList(SqList& L, int i, int e) { if (i < 1 || i > L.length + 1) { // 判断i的范围是否有效 return false; } if (L.length >= MAXSIZE) { // 判断线性表是否已满 return false; } for (int j = L.length; j >= i; j--) { // 将第i个元素之后的所有元素依次后移一个位置 L.data[j] = L.data[j - 1]; } L.data[i - 1] = e; // 将新元素插入到第i个位置 L.length++; // 线性表长度加1 return true; } ``` 删除操作的基本思路如下: 1. 判断线性表是否为空; 2. 判断待删除元素的位置是否有效; 3. 将第i个元素之后的所有元素依次前移一个位置; 4. 线性表长度减1。 具体实现可以参考下面的代码: ```c++ bool DeleteList(SqList& L, int i) { if (L.length == 0) { // 判断线性表是否为空 return false; } if (i < 1 || i > L.length) { // 判断待删除元素的位置是否有效 return false; } for (int j = i; j < L.length; j++) { // 将第i个元素之后的所有元素依次前移一个位置 L.data[j - 1] = L.data[j]; } L.length--; // 线性表长度减1 return true; } ``` 2. 链式存储结构下线性表基本操作的实现 链式存储结构是指将线性表中的元素存储在一系列的结点中,每个结点包括数据域和指针域,数据域用来存储元素,指针域用来指向下一个结点。在链式存储结构下,线性表的基本操作主要包括插入和删除两种操作。 插入操作的基本思路如下: 1. 新建一个结点; 2. 将新结点插入到第i个位置; 3. 修改前驱结点和后继结点的指针域。 具体实现可以参考下面的代码: ```c++ typedef struct LNode { int data; // 数据域 struct LNode* next; // 指针域 } LNode, * LinkList; bool InsertList(LinkList& L, int i, int e) { if (i < 1) { // 判断i的范围是否有效 return false; } LNode* p = L; // p指向头结点 int j = 0; while (p != NULL && j < i - 1) { // 寻找第i-1个结点 p = p->next; j++; } if (p == NULL) { // 判断i的范围是否有效 return false; } LNode* s = (LNode*)malloc(sizeof(LNode)); // 新建一个结点 s->data = e; // 将新结点插入到第i个位置 s->next = p->next; p->next = s; return true; } ``` 删除操作的基本思路如下: 1. 将p指向第i个结点; 2. 修改前驱结点的指针域; 3. 释放被删除结点的内存空间。 具体实现可以参考下面的代码: ```c++ bool DeleteList(LinkList& L, int i) { if (i < 1) { // 判断i的范围是否有效 return false; } LNode* p = L; int j = 0; while (p != NULL && j < i - 1) { // 寻找第i-1个结点 p = p->next; j++; } if (p == NULL || p->next == NULL) { // 判断i的范围是否有效 return false; } LNode* q = p->next; // 将p指向第i个结点 p->next = q->next; // 修改前驱结点的指针域 free(q); // 释放被删除结点的内存空间 return true; } ``` 以上就是线性表基本操作在顺序存储结构和链式存储结构下的实现方法,希望可以帮助到你。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值