1. 什么是链表
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针连接次序实现的。
每一个链表都包含多个节点,节点又包含两个部分,一个是数据域(储存节点含有的信息),一个是引用域(储存下一个节点或者上一个节点的地址)。
2. 链表的特点
获取数据麻烦,需要遍历查找,比数组慢
方便插入、删除
3. 单向链表
单向链表是一种简单的数据结构,在单向链表中每个节点中都会有一个引用域指向下一个节点的地址.
3.1 单向链表的代码实现
//单向链表实现
public class ListNode {
private Node head;
private int size = 0;
public class Node{
T data;
Node next;
public Node(T data){
this.data = data;
next = null;
}
}
//如果链表没有头结点,新结点直接成为头结点;否则新结点的next直接指向当前头结点,并让新结点成为新的头结点
public void addHeadNode(T value) {
Node newNode = new Node(value);
//头结点不存在,新结点成为头结点
if (head == null) {
head = newNode;
size ++;
return;
}
//新结点next直接指向当前头结点
newNode.next = head;
//新结点成为新的头结点
head = newNode;
size ++;
}
//果链表没有头结点,新结点直接成为头结点;否则需要先找到链表当前的尾结点,并将新结点插入到链表尾部。
public void addTailNode(T value) {
Node newNode = new Node(value);
//头结点不存在,新结点成为头结点
if (head == null) {
head = newNode;
size ++;
return;
}
//找到最后一个结点
Node last = head;
while (last.next != null) {
last = last.next;
}
//新结点插入到链表尾部
last.next = newNode;
size ++;
}
public void addNodeAtIndex(T value, int index) {
if (index < 0 || index > size) { //注意index是可以等于size()的
throw new IndexOutOfBoundsException("IndexOutOfBoundsException");
}
if (index == 0) { //插入到头部
addHeadNode(value);
} else if (index == size) { //插入到尾部
addTailNode(value);
} else { //插到某个中间位置
Node newNode = new Node(value);
int position = 0;
Node cur = head; //标记当前结点
Node pre = null; //记录前置结点
while (cur != null) {
if (position == index) {
newNode.next = cur;
pre.next = newNode;
size++;
return;
}
pre = cur;
cur = cur.next;
position++;
}
}
}
public void deleteNodeAtIndex(int index) {
if (index < 0 || index > size - 1) {
throw new IndexOutOfBoundsException("IndexOutOfBoundsException");
}
if (index == 0) { //删除头
head = head.next;
return;
}
int position = 0; //记录当前位置
Node cur = head; //标记当前结点
Node pre = null; //记录前置结点
while (cur != null) {
if (position == index) {
pre.next = cur.next;
cur.next = null; //断开cur与链表的连接
return;
}
pre = cur;
cur = cur.next;
position++;
}
}
public void get(){
Node curNode = head;
while(curNode !=null){
System.out.print(curNode.data+" ");
curNode = curNode.next;
}
System.out.println();
}
public int len(){
return this.size;
}
}
测试类
public static void main(String[] args) {
ListNode listNode = new ListNode();
listNode.addHeadNode(1);
listNode.addHeadNode(2);
listNode.addHeadNode(3);
listNode.addHeadNode(4);
listNode.addHeadNode(5);
listNode.addTailNode(6);
listNode.addNodeAtIndex(7,1);
listNode.get();
System.out.println(listNode.len());
listNode.deleteNodeAtIndex(1);
listNode.get();
}
打印结构
4. 循环链表
单向链表中的尾节点指向的下一个地址为null,在单链表中是做不到从非头节点出发访问到链表的所有的节点.
将单链表中终端节点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表成为单循环链表,简称循环链表.
4.1 循环链表的代码实现
循环链表与单向链表比较类似,只是尾部终端节点的下一个地址指向头节点即可.
//循环链表实现
public class CycleListNode {
private Node head;
private int size = 0;
public class Node{
T data;
Node next;
public Node(T data){
this.data = data;
next = null;
}
}
public boolean add(T data) {//添加节点
boolean isSuccessful=false;
if(head==null){//处理空表
Node node = new Node(data);
head=node;
node.next=head;
isSuccessful=true;
}else{//不为空时
Node node= head;
while(true){
if (node.next==head){
break;
}
node=node.next;
}
Node newNode = new Node(data);
node.next=newNode;
newNode.next=head;
isSuccessful=true;
}
return isSuccessful;
}
public Node delete(int index) {//删除制定节点
Node outputNode = null;
Node node= head; //指向第一个节点
if ((index)==0) {//查到该节点的前一个节点
outputNode = node;
head=null;
head = node.next;//删除语句
return outputNode;
}
int location=0;//计时器
while (true){ //
if (isEmpty()) {//如果链表为空中断
break;
}else if (location==index-1){//查到该节点的前一个节点
outputNode=node.next;
node.next=node.next.next;//删除语句
break;
}
if (node.next==null){
break;
}
location++;//计时器加加
node=node.next;//节点后移
}
return outputNode;
}
public void display() {//遍历整个链表
Node node = head;
while (node != null) {
System.out.println(node.data.toString());
if (node.next == head) {
break;
}
node = node.next;
}
}
public boolean isEmpty() {
return head==null;
}
}
测试
public static void main(String[] args) {
CycleListNode cycleListNode = new CycleListNode();
cycleListNode.add(1);
cycleListNode.add(2);
cycleListNode.add(3);
cycleListNode.add(4);
cycleListNode.add(5);
cycleListNode.display();
}
5. 双向链表
双向链表是在单链表的每一个节点中再设置一个指向前驱节点的指针域.所以在双向链表中的节点都有两个指针域,一个指向后继,一个指向前驱.
5.1 双向链表的代码实现
//双向链表
public class DoublyLinkedList {
private Node frist;
private Node last;
class Node{
T data;
Node next;
Node pre;
public Node(T data){
this.data = data;
}
public void displayCurrentNode() {
System.out.print(data + " ");
}
}
public DoublyLinkedList(){
frist = null;
last = null;
}
public boolean isEmpty(){
return frist == null;
}
//添加到首部
public void addFrist(T dataue){
Node newNode= new Node(dataue);
if(isEmpty()){ // 如果链表为空
last = newNode; //last -> newNode
}else {
frist.pre = newNode; // frist.pre -> newNode
}
newNode.next = frist; // newNode -> frist
frist = newNode; // frist -> newNode
}
public void addLast(T dataue){
Node newNode= new Node(dataue);
if(isEmpty()){ // 如果链表为空
frist = newNode; // 表头指针直接指向新节点
}else {
last.next = newNode; //last指向的节点指向新节点
newNode.pre = last; //新节点的前驱指向last指针
}
last = newNode; // last指向新节点
}
public boolean addBefore(T key,T dataue){
Node cur = frist;
if(frist.next.data == key){
addFrist(dataue);
return true;
}else {
while (cur.next.data != key) {
cur = cur.next;
if(cur == null){
return false;
}
}
Node newNode= new Node(dataue);
newNode.next = cur.next;
cur.next.pre = newNode;
newNode.pre = cur;
cur.next = newNode;
return true;
}
}
public void addAfter(T key,T dataue)throws RuntimeException{
Node cur = frist;
while(cur.data!=key){ //经过循环,cur指针指向指定节点
cur = cur.next;
if(cur == null){ // 找不到该节点
throw new RuntimeException("Node is not exists");
}
}
Node newNode = new Node(dataue);
if (cur == last){ // 如果当前结点是尾节点
newNode.next = null; // 新节点指向null
last =newNode; // last指针指向新节点
}else {
newNode.next = cur.next; //新节点next指针,指向当前结点的next
cur.next.pre = newNode; //当前结点的前驱指向新节点
}
newNode.pre = cur;//当前结点的前驱指向当前结点
cur.next = newNode; //当前结点的后继指向新节点
}
public void deleteFrist(){
if(frist.next == null){
last = null;
}else {
frist.next.pre = null;
}
frist = frist.next;
}
public void deleteLast(T key){
if(frist.next == null){
frist = null;
}else {
last.pre.next = null;
}
last = last.pre;
}
public void deleteKey(T key)throws RuntimeException{
Node cur = frist;
while(cur.data!= key){
cur = cur.next;
if(cur == null){ //不存在该节点
throw new RuntimeException("Node is not exists");
}
}
if(cur == frist){ // 如果frist指向的节点
frist = cur.next; //frist指针后移
}else {
cur.pre.next = cur.next;//前面节点的后继指向当前节点的后一个节点
}
if(cur == last){ // 如果当前节点是尾节点
last = cur.pre; // 尾节点的前驱前移
}else {
cur.next.pre = cur.pre; //后面节点的前驱指向当前节点的前一个节点
}
}
public T queryPre(T dataue)throws RuntimeException{
Node cur = frist;
if(frist.data == dataue){
throw new RuntimeException("Not find "+dataue+"pre");
}
while(cur.next.data!=dataue){
cur = cur.next;
if(cur.next == null){
throw new RuntimeException(dataue +"pre is not exeist!");
}
}
return cur.data;
}
public void displayForward(){
Node cur = frist;
while(cur!=null){
cur.displayCurrentNode();
cur = cur.next;
}
System.out.println();
}
public void displayBackward(){
Node cur = last;
while(cur!=null){
cur.displayCurrentNode();
cur = cur.pre;
}
System.out.println();
}
}