1、顺序表
1.1 概念及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表一般可以分为:
- 静态顺序表:使用定长数组存储。
- 动态顺序表:使用动态开辟的数组存储。
静态顺序表适用于确定知道需要存多少数据的场景.
静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。相比之下动态顺序表更灵活, 根据需要动态的分配空间大小。
1.2 接口实现
/**
* @program:
* @Author: JINLEI
* @Description:
* @Date: 2021/3/15
* @Time: 11:12
**/
class Node{
public int data; //0
public Node next; //null
public Node(int data){
this.data = data;
this.next = null;
}
}
public class MyLinkedList {
public Node head; //保存单链表的头节点的引用 null
//头插法
public void addFirst(int data){
Node node = new Node(data);
if(this.head == null){
//第一次插入数据
this.head = node;
return;
}
node.next = this.head;
this.head = node;
}
//尾插法
public void addLast(int data){
Node node = new Node(data);
if (this.head == null){
this.head = node;
return;
}
Node cur = this.head;
while (cur.next != null){
cur = cur.next;
}
cur.next = node;
}
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index, int data){
Node node = new Node(data);
if (index == 0){
addFirst(data);
return;
}
if (index == this.size()){
addLast(data);
return;
}
//先找到 index 位置的前一个节点的地址
Node cur = searchIndex(index);
node.next = cur.next;
cur.next = node;
}
private Node searchIndex(int index){
//1、对index进行合法性检查
if (index < 0 || index > this.size()){
throw new RuntimeException("index位置不合法");
}
Node cur = this.head; //index-1
while (index-1 != 0){
cur = cur.next;
index--;
}
return cur;
}
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key){
Node cur = this.head;
while (cur != null){
if (cur.data ==key){
return true;
}
cur = cur.next;
}
return false;
}
//查找前驱
private Node searchPrev(int key){
Node prev = this.head;
while (prev.next != null){
if (prev.next.data ==key){
return prev;
}else {
prev = prev.next;
}
}
return null;
}
//删除第一次出现的关键字为key的节点
public void remove(int key){
if (this.head == null){
return;
}
//删除的是不是头节点
if (this.head.data == key){
this.head = this.head.next;
return;
}
//找到删除节点的前驱
Node prev = searchPrev(key);
if (prev == null){
System.out.println("没有此节点");
return;
}
//开始删除
Node del = prev.next;
prev.next = del.next;
}
//删除所有值为key的节点
public void removeAllKey(int key){
Node prev = this.head;
Node cur = this.head.next; //代表要删除的节点
while (cur != null){
if (cur.data == key){
prev.next = cur.next;
cur = cur.next;
}else {
prev = cur;
cur = cur.next;
}
}
if (this.head.data == key){
this.head = this.head.next;
}
}
//得到单链表的长度
public int size(){
int count = 0;
Node cur = this.head;
while(cur != null){
count++;
cur = cur.next;
}
return count;
}
//打印单链表
public void display(){
Node cur = this.head;
while (cur != null){
System.out.print(cur.data+" ");
cur = cur.next;
}
System.out.println();
}
}
1.3 顺序表的问题
- 顺序表中间/头部的插入删除,时间复杂度为O(N)
- 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
- 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
2、链表
2.1 链表的概念
链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
- 单向、双向、
- 带头、不带头
- 循环、非循环
重点掌握两种:
-
无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
-
无头双向链表:在Java的集合框架库中LinkedList底层实现就是无头双向循环链表。
2.2 链表的实现
/**
* @program:
* @Author: JINLEI
* @Description:
* 无头双向链表
* @Date: 2021/3/25
* @Time: 15:49
**/
class Node{
public int data; //数据
public Node next; //后继信息
public Node prev; //前驱信息
public Node (int data){
this.data = data;
}
}
public class MyLinkedList {
public Node head; //双向链表的头
public Node tail; //双向链表的尾
//无头双向链表头插法
public void addFirst(int data){
Node node = new Node(data);
if (this.head == null) {
//第一次插入数据
this.head = node;
this.tail = node;
}else {
node.next = this.head;
this.head.prev = node;
this.head = node;
}
}
//无头双向链表尾插法
public void addLast(int data){
Node node = new Node(data);
if (this.head == null){
//第一次插入
this.head = node;
this.tail = node;
}else {
this.tail.next = node;
node.prev = this.tail;
this.tail = node;
}
}
//打印无头双向链表
public void display(){
Node cur = this.head;
while (cur != null){
System.out.print(cur.data+ " ");
cur = cur.next;
}
System.out.println();
}
//查找是否包含关键字key是否在双向链表中
public boolean contains(int key){
Node cur = this.head;
while (cur != null ){
if (cur.data == key) {
return true;
}
cur = cur.next;
}
return false;
}
//得到双向链表的长度
public int size(){
int count = 0;
Node cur = this.head;
while (cur != null){
count++;
cur = cur.next;
}
return count;
}
private void checkIndex(int index){
if (index < 0 || index > size()){
throw new RuntimeException("index不合法");
}
}
private Node searchIndex(int index){
Node cur = this.head;
while (index != 0){
cur = cur.next;
index--;
}
return cur;
}
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index, int data){
checkIndex(index);
if (index == 0){
addFirst(data);
return ;
}
if (index == size()){
addLast(data);
return ;
}
Node cur = searchIndex(index);
Node node = new Node(data);
node.next = cur;
node.prev = cur.prev;
cur.prev.next = node;
cur.prev = node;
}
//删除第一次出现的关键字为key的节点
public int remove(int key){
Node cur = this.head;
while (cur != null){
if (cur.data == key){
int oldData = cur.data;
// 删除的是头节点
if (cur == this.head){
this.head = this.head.next;
this.head.prev = null;
}else {
//不是头节点
cur.prev.next = cur.next;
//判断是不是尾节点
if (cur.next != null){
cur.next.prev = cur.prev;
}else {
this.tail = cur.prev; //删除的尾巴节点 只需要tail 前移
}
}
return oldData;
}
cur = cur.next;
}
return -1;
}
//删除所有值为key的节点
public void removeAllKey(int key){
Node cur = this.head;
while (cur != null){
if (cur.data == key){
int oldData = cur.data;
// 删除的是头节点
if (cur == this.head){
this.head = this.head.next;
if (this.head != null) {
this.head.prev = null;
}
}else {
//不是头节点
cur.prev.next = cur.next;
//判断是不是尾节点
if (cur.next != null){
cur.next.prev = cur.prev;
}else {
this.tail = cur.prev; //删除的尾巴节点 只需要tail 前移
}
}
}
cur = cur.next;
}
}
/**
* 清除双向链表
* 一个一个节点进行释放
*/
public void clear(){
while (this.head != null){
Node cur = this.head.next;
this.head.prev = null;
this.head.next = null;
this.head = cur;
}
this.tail = null;
}
}