Java实现顺序表和链表结构
目录
- 前言:
- 顺序表
- 定义:
- 实现方法:
- 代码实现:
- 链表
- 定义:
- 分类:
- 实现方法:
- 代码实现:
- 顺序表 & 链表
- 总结
前言:
线性表(linear list)是n个具有相同特性的数据元素的有限序列。
线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串。
顺序表
定义:
用一段物理地址连续的存储单元依次存储数据元素的线性结构(逻辑上连续,物理上也连续)
(1)静态顺序表:使用定长数组存储。
(2)动态顺序表:使用动态开辟的数组存储
【注意】静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用,所以相比之下,动态数组更为灵活,可根据需要动态分配空间大小
实现方法:
增、删、改、查
增加操作:从头部插入、从尾部插入、在任意索引位置处插入
删除操作:根据索引删除元素、根据元素值删除第一个出现的该元素、根据元素值删除所有该值元素
查找操作:根据元素值查找是否存在某元素、根据索引下标返回该处元素值、根据元素值返回索引下标
修改操作:根据索引下标修改该处元素
代码实现:
public class MyArray { private int[]data; private int size; // 无参构造 public MyArray(){ this.data=new int[5]; } // 有参构造 public MyArray(int n){ this.data=new int[n]; } // grow方法用于扩充内存 private void grow() { int[] newdata= Arrays.copyOf(data,size*2); this.data=newdata; } // toString方法输出顺序表内容 public String toString(){ String str="["; for (int i = 0; i <size ; i++) { str+=data[i]; if(i!=size-1){ str+=","; } } str+="]"; return str; } // 尾插法: public void addLast(int value){ if(size== data.length){ grow(); } data[size]=value; size++; } // 头插法: public void addFirst(int value){ if(size==data.length){ grow(); } for (int i = size-1; i >=0; i--) { data[i+1]=data[i]; } data[0]=value; size++; } // 中间任意位置插入: public void addIndex(int index,int value){ if(size==data.length) grow(); if(index<0||index>size){ System.err.println("插入位置不合理!"); return; } else { for (int i = size - 1; i >= index; i--) { data[i + 1] = data[i]; } data[index] = value; size++; } } // 查看当前数组中是否存在该元素 public boolean contains(int value){ for (int i = 0; i < size; i++) { if(data[i]==value) return true; } return false; } // 查找当前数组元素对应的下标 public int getIndex(int value){ for (int i = 0; i < size; i++) { if(data[i]==value){ return i; } } return -1; } // 查找数组下标为index的元素 public int getValue(int index) { if (index < 0 || index > size - 1) { System.out.println("输入下标不合理"); return -1; } return data[index]; } // 根据索引删除元素,注意将最后一个元素置为0 public void removeIndex(int index){ if(index<0||index>=size){ System.err.println("输入不合法!"); } for (int i = index; i <size-1; i++) { data[i]=data[i+1]; } size--; data[size]=0; } // 删除第一个元素值为value的元素 public void removeValueOnce(int value){ int a=getIndex(value); removeIndex(a); } // 删除所有元素值为value的元素 public void removeValueAll(int value){ for (int i = 0; i < size; i++) { while(i!=size||data[i]==value) removeIndex(i); } } // 根据索引修改元素 public void recompose(int index,int newValue){ if(index<0||index>=size){ System.err.println("输入不合法!"); } data[index]=newValue; } }
链表
定义:
逻辑上连续,物理上非连续存储。
链表由一个个节点组成,每个节点存储该节点处的元素值val 以及下一个节点的地址next,由地址next实现逻辑上连续
分类:
分类方式:
(1)单链表、双链表
(2)带虚拟头节点、不带虚拟头节点
(3)循环链表、非循环链表
按不同分类方式组合有8种:
非循环四种:
(1)不带虚拟头节点的单向链表(非循环)
(2)带虚拟头节点的单向链表(非循环)
(3)不带虚拟头节点的双向链表(非循环)
(4)带虚拟头节点的双向链表(非循环)
循环四种:
(5)不带虚拟头节点的单向链表(循环)
(6)带虚拟头节点的单向链表(循环)
(7)不带虚拟头节点的双向链表(循环)
(8)带虚拟头节点的双向链表(循环)
其中:
(1)不带虚拟头节点的单向链表(非循环):结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。这种结构在笔试面试中出现很多
(7)不带虚拟头节点的双向链表(循环):在Java的集合框架库中LinkedList底层实现就是无头双向循环链表
实现方法:
增、删、查、改 和顺序表实现方法基本一样;
唯一注意:带虚拟头节点与不带虚拟头节点相比,带虚拟头节点避免了考虑头节点为空的特殊情况
代码实现:
(1)不带虚拟头节点的单链表:
class Node { // val表示存储的数值,next表示下一个节点的地址 int val; Node next; // 构造方法 public Node(int val) { this.val = val; } } public class SingleList { // size表示节点个数 // head表示头节点 private int size; private Node head; //定义toString方法以输出链表内容 public String toString() { String str = ""; Node node = head; while (node != null) { str += node.val; str += "->"; node = node.next; } str += null; return str; } //将判断输入的索引是否合法抽象为方法islegal public boolean islegal(int index) { if (index < 0 || index > size) { return false; } else { return true; } } // 头插法 public void addHead(int value) { Node node = new Node(value); if (head == null) { head = node; } else { node.next = head; head = node; } size++; } // 中间任意位置插入 public void addIndex(int index, int val) { if (!islegal(index)) { System.out.println("输入数据不合法!"); return; } if (index == 0) { addHead(val); return; } Node node = new Node(val); Node pre = head; for (int i = 0; i < index - 1; i++) { pre = pre.next; } node.next = pre.next; pre.next = node; size++; return; } // 尾插法 public void addLast(int val) { if (head == null) { addHead(val); } else { addIndex(size, val); } } // 删除指定索引位置的元素 public void removeIndex(int index) { if (islegal(index)) { if (index == 0) { Node temp = head; head = head.next; temp.next = null; size--; return; } else { Node pre = head; for (int i = 0; i < index - 1; i++) { pre = pre.next; } Node cur = pre.next; pre.next = cur.next; cur.next = null; size--; } } else { System.out.println("输入数据不合法!"); } } // 根据元素值删除元素,且只删除第一次出现的该元素 public void removeValueOnce(int val) { if (head.next != null && head.val == val) { removeIndex(0); } else { Node pre = head; while (pre.next != null) { if (pre.next.val == val) { pre.next = pre.next.next; pre.next.next = null; size--; return; } pre = pre.next; } } } // 根据元素值删除元素,且删除所有该元素 public void removeValueAll(int val) { while (head.next != null && head.val == val) { Node temp = head; head = head.next; temp = null; size--; } if (head == null) { return; } else { Node pre = head; while (pre.next != null) { if (pre.next.val == val) { pre.next = pre.next.next; size--; return; } else { pre = pre.next; } } } } // 根据元素值删除元素,且删除所有该元素并返回头节点(带虚假节点) public Node removeValueAllWithDummy(int val) { Node dummyHead = new Node(-1); dummyHead.next = head; Node pre = dummyHead; while (pre.next != null) { if (pre.next.val == val) { Node cur = pre.next; pre.next = cur.next; cur.next = null; size--; } else { pre = pre.next; } } return dummyHead.next; } // 根据索引查元素值 public int get(int index) { if (islegal(index)) { Node cur = head; for (int i = 0; i < index; i++) { cur = cur.next; } return cur.val; } else { System.out.println("输入数据不合法!"); return -1; } } // 能否查到给定的某元素值(自己写的,好像很复杂) public boolean contains(int val) { boolean a = false; if (head == null) { System.out.println("该链表为空!"); return false; } else { Node node = head; for (int i = 0; i < size; i++) { if (node.val == val) { a = true; } node = node.next; } } return a; } // 能否查到给定的某元素值,老师写的方法 public boolean contains2(int val) { for (Node temp = head; temp != null; temp = temp.next) { if (temp.val == val) { return true; } } return false; } // 根据索引修改元素值 public void set(int index, int newValue) { if (islegal(index)) { Node node = head; for (int i = 0; i < index; i++) { node = node.next; } node.val = newValue; return; } System.out.println("输入数据不合法!"); } }
(2)带虚拟头节点的单链表
以在指定索引位置插入元素为例,理解虚拟头节点的作用即可
public class SingleListWithDummyHead { Node dummyNode=new Node(-1); int size; // 在指定位置插入元素,因为有虚拟头节点故不用考虑index=0的情况,全部为在中间位置插入的情况 public void addIndex(int index,int value){ // 先判断index是否合法 if(index<0||index>size){ System.out.println("illegal"); return; } Node a=new Node(value); Node pre=dummyNode; for(int i=0;i<index;i++){ pre=pre.next; } a.next=pre.next; pre.next=a; size++; } }
(3)带虚拟头节点的双链表
public class DoubleLinkedList { private int size; private Node dummyHead = new Node(-1); private Node tail; // 定义toString方法用以方便输出 public String toString() { String s = ""; Node node = dummyHead.next; while (node != null) { s = s + node.val; s = s + "->"; node = node.next; } s += "null"; return s; } // 判断输入的索引是否合法 private boolean isRange(int index) { if (index < 0 || index >= size) { System.out.println("输入不合法!"); return false; } else return true; } // 头插法 public void addHead(int val) { Node a = new Node(val, dummyHead, dummyHead.next); //链表为空 if (dummyHead.next == null) { tail = a; dummyHead.next = a; } // 否则链表不为空 else { dummyHead.next.prev = a; dummyHead.next = a; } size++; } // 尾插法 public void addTail(int val) { Node a = new Node(val, tail, null); // 链表为空 if (dummyHead.next == null) { dummyHead.next = a; } // 链表不为空 else { tail.next = a; } tail = a; size++; } // 中间位置插入 public void addMiddle(int index, int val) { // 判断插入位置合理否 if (index < 0 || index > size) { System.out.println("输入不合法!"); } // 头部插入 else if (index == 0) { addHead(val); } // 尾部插入 else if (index == size) { addTail(val); } // 中间任意位置插入 else { //先找到要插入位置的前一个元素,可另一个方法找该元素 Node a = new Node(val, find(index), find(index).next); find(index).next.prev = a; find(index).next = a; size++; } } // 这里find的方法是找到index位置的前一个节点元素 public Node find(int index) { Node f = dummyHead; for (int i = 0; i < index; i++) { f = f.next; } return f; } // 根据索引删除指定位置的元素 public void removeIndex(int index) { if (index < 0 || index >= size) { System.out.println("输入不合法!"); return; } else { find(index).next.next.prev = find(index); find(index).next = find(index).next.next; size--; } } // 删除指定节点 private void deleteNode(Node node) { // 分治思想,先处理node与左边节点,再处理node与右边节点 Node pre = node.prev; Node next = node.next; // 处理左边,因为有虚拟头节点,故不用另讨论为头节点的情况 pre.next = next; node.prev = null; // 处理右边 if (next == null) { tail = pre; } else { next.prev = pre; node.next = null; } size--; } // 删除指定元素值(只删除第一次出现的) public void removeValueOnce(int val) { for (Node a = dummyHead.next; a != null; a = a.next) { if (a.val == val) { deleteNode(a); return; } } System.out.println("链表中无该元素故无法删除"); } public void removeValueAll(int val) { for (Node a = dummyHead.next; a != null; ) { if (a.val == val) { Node b = a.next; deleteNode(a); a = b; } else a = a.next; } } // 根据索引查找元素值 public int get(int index) { if (isRange(index)) { return find(index).next.val; } else { return -1; } } // 查找是否存在某元素 public boolean contains(int val) { Node a = dummyHead; while (a.next != null) { if (a.next.val == val) { return true; } a = a.next; } return false; } // 修改,将指定位置元素修改为另一值 public void set(int index, int newValue) { if (isRange(index)) { find(index).next.val = newValue; } } } //节点类 class Node { int val; Node prev; Node next; public Node(int val) { this.val = val; } public Node(int val, Node prev, Node next) { this.val = val; this.prev = prev; this.next = next; } }
顺序表 & 链表
顺序表:
优点:空间连续、支持随机访问
缺点:中间或前面部分的插入删除时间复杂度O(N)
增容的代价比较大。
链表:
优点:任意位置插入删除时间复杂度为O(1)
没有增容问题,插入一个开辟一个空间
缺点:以节点为单位存储,不支持随机访问
总结
到此这篇关于Java实现顺序表和链表结构的文章就介绍到这了,更多相关Java顺序表和链表内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!