线性表的定义
零个或多个数据元素的有限序列。
线性表的抽象数据类型:
package com.mylist;
public interface MyList<E> extends Iterable<E> {
//添加元素
public void add(E e);
//指定位置添加元素
public void add(int index, E e);
//清空集合
public void clear();
//删除第一个匹配的元素,并将所有后面的元素左移
public boolean remove(E e);
//返回删除元素,并将所有元素左移
public E remove(int index);
//修改特定位置的元素,并返回原理的元素
//之所以是object类型的是因为,原来的元素不一定和当前元素同一类型
public Object set(int index, E e);
//如果不包含返回-1
public int indexOf(E e);
//最后一次包含的位置
public int lastIndexOf(E e);
//根据索引获取元素
public E get(int index);
//是否包含该元素
public boolean contains(E e);
//是否为空
public boolean isEmpty();
//集合大小
public int size();
}
package com.mylist;
/*
* 18.7.21
* 线性表实现练习,定义抽象类实现部分方法,
* isEmpty,size,add(E),remove(E)
*/
public abstract class MyAbstractList<E> implements MyList<E> {
protected int size = 0;
//抽象类的构造方法必须是protect类型,由子类继承实现,本身不能实例化
protected MyAbstractList() {
}
protected MyAbstractList(E[] objects) {
for(int i = 0; i < objects.length; i++) {
add(objects[i]);
}
}
@Override
public void add(E e) {
add(size,e);
}
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public boolean remove(E e) {
if(indexOf(e) >= 0) {
remove(indexOf(e));
return true;
}
else
return false;
}
}
线性表顺序存储结构
线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。
顺序存储方式:一维数组实现顺序存储机构
数据长度和线性表长度:
数据长度值得是分配的空间大小,一把分配之后是固定不变的。
线性表长度指的是已经存在的数据量大小。
线性表长度是线性表中元素的个数,随着线性表插入和删除操作的进行,这个量是变化的。
在任意时刻,线性表的长度应该小于数据的长度。
顺序存储结构的插入与删除
插入算法思路
如果插入位置不合理,抛出异常。
如果线性表的长度>=数组长度,则抛出异常或动态增加容量。
从最后一个元素开始向前遍历到第i个位置,分别将他们依次向后移动一个位置。
将要插入的元素填入i位置。
表长+1。
删除算法思路
如果删除位置不合理,抛出异常。
取出被要删除的元素。
从删除元素位置开始遍历到最后一个元素位置,分别将他们都向前移动一个位置。
表长-1。
package com.myarraylist;
import com.mylist.MyAbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
public class MyArrayList<E> extends MyAbstractList<E> {
//设置初始容量大小
public static final int INITIAL_CAPACITY = 16;
//由于范型消除,不能直接使用new E[INITIAL_CAPACITY]而是使用了类型转换
private E[] data = (E[]) new Object[INITIAL_CAPACITY];
//用于判断数组在执行add操作之后不会越界
private void ensureCapacity(){
if(size>=data.length){
//数组扩容为 2*size +1
E[] newData = (E[]) new Object[2*size+1];
System.arraycopy(data,0,newData,0,size);
data = newData;
}
}
public MyArrayList() {
}
public MyArrayList(E[] data) {
this.data = data;
}
//指定位置添加元素
@Override
public void add(int index, E e) {
//首先进行判断
ensureCapacity();
//将指定位置后(包括该位置)的数组元素一次往后移
for(int i=size-1;i>=index;i--){
data[i+1] = data[i];
}
data[index] = e;
size++;
}
//清空线性表
@Override
public void clear() {
data = (E[]) new Object[INITIAL_CAPACITY];
size = 0;
}
//检查指定位置的是否越界
private void checkIndex(int index){
if(index<0 || index>=size){
throw new IndexOutOfBoundsException("index"+index+" out of bounds");
}
}
//删除指定位置的元素
@Override
public E remove(int index) {
checkIndex(index);
E e = data[index];
//知道位置之后左移
for(int i=index;i<size-1;i++){
data[i] = data[i+1];
}
data[size-1] = null;
size--;
return e;
}
//修改指定位置的值
@Override
public Object set(int index, E e) {
checkIndex(index);
E old = data[index];
data[index] = e;
return old;
}
//如果不包含返回-1
@Override
public int indexOf(E e) {
for(int i=0;i<size;i++){
if(data[i]==e){
return i;
}
}
return -1;
}
@Override
public int lastIndexOf(E e) {
for(int i=size-1;i>=0;i++){
if(data[i]==e){
return i;
}
}
return -1;
}
@Override
public E get(int index) {
checkIndex(index);
return data[index];
}
@Override
public boolean contains(E e) {
for(int i=0;i<size;i++){
if(data[i]==e){
return true;
}
}
return false;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder("[");
for(int i=0;i<size;i++){
result.append(data[i]);
if(i<size-1)
result.append(",");
}
result.append("]");
return result.toString();
}
//将数组调整到合适大小 即数组大小==数据量
public void trimToSize() {
if(size!=data.length){
E[] newData = (E[]) new Object[size];
System.arraycopy(data,0,newData,0,size);
data = newData;
}
}
@Override
public Iterator<E> iterator() {
return new ArrayListIterator();
}
private class ArrayListIterator implements Iterator<E> {
private int current = 0;//获取当前后变成下一个
private int lastSet = -1;//删除用它 它表示当前
@Override
public boolean hasNext() {
return (current<size);
}
@Override
public E next() {
lastSet = current;
return (data[current++]);
}
//测试提交gitee啊哈哈
@Override
public void remove() {
//当在一个类的内部类中,如果需要访问外部类的方法或者成员域
//就要必须使用 外部类.this.成员域
MyArrayList.this.remove(lastSet);
//删除后长度-1
current = lastSet;//-1即lastSet
lastSet = -1;//变回原来的值
}
}
}
package com.myarraylist;
import com.mylist.MyList;
import java.util.Iterator;
import java.util.List;
public class MyArrayListTest {
public static void main(String[] args) {
MyList<String> list = new MyArrayList<String>();
//添加数据
list.add("0");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
System.out.println("list初始化");
System.out.println(list);
//指定位置添加数据
list.add(1,"1");
System.out.println("指定位置添加数据");
System.out.println(list);
//删除指定位置元素
list.remove(0);
System.out.println("删除指定位置元素");
System.out.println(list);
//修改指定位置元素
list.set(4,"1");
System.out.println("修改指定位置元素");
System.out.println(list);
//获取制定位置的元素
System.out.println("获取制定位置的元素");
System.out.println(list.get(3));
System.out.println("返回目标元素的第一次的位置");
System.out.println(list.indexOf("1"));
System.out.println("返回目标元素的最后一次的位置");
System.out.println(list.lastIndexOf("1"));
System.out.println("判断是否包含目标元素");
System.out.println(list.contains("1"));
System.out.println(list);
for(String str:list){
System.out.print(str+" ");
if(str.equals("4")){
list.remove(str);
}
}
System.out.println("");
System.out.println(list);
Iterator<String> it = list.iterator();
while (it.hasNext()){
String next = it.next();
if("1".equals(next)){
it.remove();
}
System.out.println("----");
System.out.println(list);
}
System.out.println(list);
}
}
list初始化
[0,2,3,4,5]
指定位置添加数据
[0,1,2,3,4,5]
删除指定位置元素
[1,2,3,4,5]
修改指定位置元素
[1,2,3,4,1]
获取制定位置的元素
4
返回目标元素的第一次的位置
0
返回目标元素的最后一次的位置
4
判断是否包含目标元素
true
[1,2,3,4,1]
1 2 3 4
[1,2,3,1]
----
[2,3,1]
----
[2,3,1]
----
[2,3,1]
----
[2,3]
[2,3]
Disconnected from the target VM, address: '127.0.0.1:65498', transport: 'socket'
线性表的链式存储结构
结点:
为了表示每个数据元素a(i)与其直接后继数据元素a(i+1)之间的逻辑关系,对数据元素a(i)来说,除了存储其本身的信息外,还需要存储一个指示其后继的信息(即直接后继的存储位置)。我们把存储元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称为指针或链。这两部分信息组成数据元素ai的存储影像,称为结点(Node)。
单链表:
n个结点(ai的存储映像)链结成一个链表,即为线性表(a1,a2,a3,.....an)的链式存储结构,因此此链表的每个结点中只包含一个指针域,所以叫做单链表。
单链表真是通过每个结点的指针域将线性表的数据元素按其逻辑次序链接在一起,如下图所示。
对于线性表来说,总得有个头有个尾,链表也是。我们把链表中第一个结点的存储位置叫作头指针,那么整个链表的存取就必须从头指针开始进行了。之后的每个结点上,其实就是上一个指针指向的位置。
链表的最后一个结点指针为空,通常用NULL或"^"表示。
有时,我们为了更加方便地对链表进行操作,会在单链表的第一个结点前附设一个结点,称为头结点。头结点的数据域不存储任何信息。也可以存储线性表的长度等附加信息,头结点的指针域存储指向第一个结点的指针。
头指针与头结点的区别
头指针 | 头结点 |
---|---|
头指针是指链表中指向第一个结点的指针,若链表中存在头结点,则是指向头结点的指针。 | 头结点是为了操作统一和方便而设立的,放在第一元素的节点之前,其数据域一般无意义(也可存放链表的长度)。 |
头指针具有标识作用,所以常用头指针冠以链表的名字 | 有了头结点,对在第一元素结点前插入节点和删除元素第一结点,其操作与其他节点的操作就统一了。 |
无论链表是否为空,头指针均不为空。头指针是链表的必要元素。 | 头结点不一定是链表的必要元素。 |
![]() | ![]() |
单链表的读取:
声明一个结点p指向链表的第一个结点,初始化j从1开始。
当j<i时,就遍历链表,让p的指针向后移动,不断的指向写一个结点,j累加1。
若到链表尾p为空,则说明第i个元素不存在。
否则查找成功,返回结点p的数据。
单链表的插入与删除
插入
声明一结点P指向链表的第一个结点,初始化j从1开始。
当j<i时,就遍历链表,让p的指针向后移动,不断地指向下一个结点,j累加1;
若到链表结尾p为空,则说明第i个元素不存在。
否则查找成功,在系统中生成一个空节点s。
将数据元素e赋值给 s.data
单链表的插入语句 s.next = p.next p.next = s
返回成功。
删除
声明一结点P指向链表的第一个结点。
当j<i 时,就遍历链表,让P指针向后移动,不断地指向下一个结点,j累加1。
若到链表末尾p为空,则说明第i个元素不存在。
否则查找成功,欲将删除的结点p.next 赋值给q。
单链表的标准删除语句: p.next = q.next (等于跳过了q)
将q结点中的数据赋值给e,作为返回。
释放q结点。
返回成功。
单链表的整表创建
声明一结点p和计数器变量 i 。
初始化一空链表L。
让L的头结点的指针指向NULL,即建立一个带有头结点的单链表。
循环:
生成一个新结点赋值给p;
随机生成一数字赋值给p的数据域p.data
将p插入到头结点与前一新结点之前。
单链表的整表删除
声明一结点p和q;
将第一个结点赋值给p;
循环:
将下一结点赋值给q;
释放p;
将q赋值给p
package com.mylinkedlist;
public class Node<E> {
public E element;
Node<E> next;
public Node(E element) {
this.element = element;
}
public Node(E element, Node<E> next) {
this.element = element;
this.next = next;
}
}
package com.mylinkedlist;
import com.mylist.MyAbstractList;
import java.util.Iterator;
import java.util.LinkedList;
/**
* 实现链表练习,继承自相同接口和抽象类,新添加的方法包括
* addFirst、addLast、removeFirst、removeLast、getFirst、getLast
* 按照增删改查的顺序实现方法
*/
public class MyLinkedList<E> extends MyAbstractList<E> {
private Node<E> head,tail;
public MyLinkedList() {
}
public MyLinkedList(E[] object) {
super(object);
}
//在指定位置添加e
@Override
public void add(int index, E e) {
if(index<=0){
addFirst(e);
}else if (index>=size){
addLast(e);
}else {
//获取前一个元素
Node<E> oldPreNode = head;
//获取到他的上一个
for (int i = 0; i < index-1; i++) {
oldPreNode = oldPreNode.next;
}
Node<E> oldNode = oldPreNode.next;
Node<E> newNode = new Node<>(e);
oldPreNode.next = newNode;
oldPreNode.next.next = oldNode;
size++;
}
}
//增加头节点
public void addFirst(E e){
Node<E> newNode = new Node<>(e);
newNode.next = head;//新的node指向之前的头
head = newNode;//新的node为头
size++;
//只有一个节点 即新增为第一次增加
if(tail == null){
tail = head;
}
}
//增加尾节点
public void addLast(E e){
Node<E> newNode = new Node<>(e);
//如果是空的
if(tail==null){
tail = newNode;
head = tail;
}else {
tail.next =newNode;//之前的最后一个指向newNode
tail = newNode;//最后一个变为newNode
}
size++;
}
//清除元素
@Override
public void clear() {
size = 0;
tail = head = null;
}
//删除头结点
public E removeFirst() {
//如果链表为空
if(size==0){
return null;
}else{
Node<E> temp = head;
head = temp.next;
if(head==null){
tail=null;
}
size--;
return temp.element;
}
}
//删除尾结点
public E removeLast() {
//如果链表是空
if(size==0){
return null;
}else if(size==1) {
Node<E> temp = head;
head = tail = null;
size --;
return temp.element;
}else{
Node<E> temp = tail;
//获取尾节点之前的元素
Node<E> current = head;
for(int i = 0;i<size-2;i++){
current = current.next;
}
tail = current;//尾点之前的元素变成尾节点
tail.next = null;
size--;
return temp.element;
}
}
//删除指定位置的元素
@Override
public E remove(int index) {
if(index<0 || index>=size){
return null;
}else if(index ==0 ){
return removeFirst();
}else if(index == size-1){
return removeLast();
}else {
Node<E> previous = head;
//获取到他的上一个
for (int i=0;i<index-1;i++){
previous = previous.next;
}
Node<E> targetNode = previous.next;//要删除的节点
//他的前一个指向他的下一个
previous.next = targetNode.next;
size--;
return targetNode.element;
}
}
//指定位置重新赋值
@Override
public Object set(int index, E e) {
if(index<0 || index>=size){
return null;
}else {
Node<E> targetNode = head;
//获取到他的上一个
for (int i = 0; i < index; i++) {
targetNode = targetNode.next;
}
targetNode.element = e;
return targetNode.element;
}
}
//返回第一个符合条件的节点
//返回第一个符合条件的结点,没有则返回-1
@Override
public int indexOf(E e) {
Node<E> current = head;
for(int i=0;i<size;i++){
if(current.element ==e){
return i;
}
current = current.next;
}
return -1;
}
//返回从前往后查找符合条件的最后一个结点
@Override
public int lastIndexOf(E e) {
int target = -1;
Node<E> current = head;
for(int i=0;i<size;i++){
if(current.element ==e){
target = i;
}
current = current.next;
}
return target;
}
//根据索引获取值
@Override
public E get(int index) {
if(index < 0 || index > size -1) return null;
Node<E> current = head;
for(int i=0;i<size;i++){
current = current.next;
}
return current.element;
}
public E getFirst() {
if(size == 0) {
return null;
}
else {
return head.element;
}
}
public E getLast() {
if(size == 0) {
return null;
}
else {
return tail.element;
}
}
@Override
public String toString() {
StringBuilder result = new StringBuilder("[");
Node<E> current = head;
for(int i = 0; i < size; i++) {
result.append(current.element);
current = current.next;
if(current != null) {
result.append(",");
}
else {
result.append("]");
}
}
return result.toString();
}
@Override
public boolean contains(E e) {
Node<E> current = head;
for(int i = 0; i < size; i++) {
if(current.element == e) {
return true;
}
current = current.next;
}
return false;
}
@Override
public Iterator<E> iterator() {
return new MyLinkedListIterator();
}
private class MyLinkedListIterator implements Iterator<E>{
private Node<E> current = head;
@Override
public boolean hasNext() {
return (current != null);
}
@Override
public E next() {
E e = current.element;
current = current.next;
return e;
}
// @Override
// public E remove() {
//
// }
}
}
package com.mylinkedlist;
import com.mylist.MyList;
import java.util.List;
public class TestMyLinkedList {
public static void main(String[] args) {
MyLinkedList<String> myLinkedList = new MyLinkedList<String>();
myLinkedList.add("0");
myLinkedList.add("1");
myLinkedList.add("2");
System.out.println(myLinkedList);
myLinkedList.addFirst("first");
myLinkedList.addLast("last");
System.out.println(myLinkedList);
myLinkedList.set(1,"新赋值1");
System.out.println(myLinkedList);
myLinkedList.add(1,"新添加1");
System.out.println(myLinkedList);
myLinkedList.remove(1);
System.out.println(myLinkedList);
}
}
单链表结构与顺序存储结构的优缺点
\ | 顺序存储结构 | 单链表 |
---|---|---|
存储分配方式 | 用一段连续的存储单元依次存储线性表的数据元素。 | 采用链式存储,用一组任意存储单元存放线性表的元素。 |
时间性能 | 查找:顺序存储结构O(1);插入和删除:需要平均移动表长一半的元素时间为O(n) | 单链表O(n);插入和删除:找出某位置的指针后,插入和删除的时间仅为O(1) |
空间性能 | 需要预分配存储空间,分大了,浪费;分小了,容易发生上溢。 | 不需要分配存储空间,只要有就可以分配,元素的个数也不受限制。 |
静态链表
数组式的链表,每个结点指向的不是下一个结点的指针,而是指向在数组中的索引。
静态链表优缺点:
优点 | 缺点 |
---|---|
在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进了在顺序存储中的插入和删除操作需要移动大量元素的缺点。 | 没有解决连续存储分配带来的表长难以确定的问题。失去了顺序存储结构随机存储的特点。 |
循环链表
单链表尾结点无后继,尾结点的指针指向空,将尾结点得到指针端由空指针改为指向头结点,就使整个单链表形成了一个环,这种头尾相互接的单链表称为单链表循环,简称链表循环。
双向链表
双向链表是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。
参考书籍《大话数据结构》