一、链表与顺序表区别
二、链表的结构
1、带头节点
2、不带头节点
三、实现一个带头节点的链表
方法:
1、增加(头插、尾插)
2、删除某个节点
3、输出链表
4、得到链表指定位置节点
5、逆置链表
6、得到链表长度
四、不带头结点链表
方法:
1、增加(头插、尾插)
2、删除某个节点
3、输出链表
4、得到链表指定位置节点
5、逆置链表
6、得到链表长度
7、查找链表倒数第k个节点
8、合并两个有序链表
9、逆序输出链表
10、不允许遍历链表,在 pos 前插入一个节点
一、链表与顺序表区别
顺序表:
底层实现是数组,是一段连续的内存空间;
遍历和查找元素比较容易,插入删除比较复杂;
数组的大小确定后,要更改需要花费较大的代价。
链表:
对于每一个链表来说,都有一个数据域和一个引用域,这个引用域是链表下一个节点的引用;
对于链表来说,插入删除元素容易,遍历链表相对顺序表来说困难;
同时,链表的物理位置不确定,可能是一段连续的内存空间、也可能不是一块连续的内存空间,由每个节点的引用域确定。
二、链表的结构
这里有一个技巧,也就是设计链表时用到的一个名叫头节点的东西–>head。
在带头节点的链表中,头节点的意义是:在单链表的第一个结点之前附设的一个结点
不带头节点的链表中,头节点仅仅只是标记着链表中第一个元素的位置。
1、带头节点
2、不带头节点
三、实现一个带头节点的链表
class linkedListWithHead<T> {
// head 是指向链表中第一个节点的引用
protected Node<T> head;
class Node<E> {
protected E element;//数据域
protected Node<E> next;//引用域
public Node(E element) {
this.element = element;
}
}
//为 head 开辟空间
public linkedListWithHead() {
head = new Node<>((T) new Object());
}
//头插
public void addHead(T data) {
//创建一个新节点
Node<T> newNode = new Node<>(data);
//绑定新节点
newNode.next = head.next;
head.next = newNode;
}
//尾插
public void addTail(T data) {
//创建一个新节点
Node<T> newNode = new Node<>(data);
Node<T> temp = head;
//先遍历,找到最后一个节点
while (temp.next != null) {
temp = temp.next;
}
//绑定新节点
temp.next = newNode;
}
//输出链表 重写 toString()方法
public String toString() {
StringBuilder sbr = new StringBuilder();
Node<T> temp = head;
while (temp.next != null) {
sbr.append(temp.next.element + " ");
temp = temp.next;
}
return sbr.toString();
}
//删除某个值
public boolean delete(T data) {
if (head.next == null) return false;
//判断头节点是不是要被删除的元素
if (head.element == data) {
head = head.next;
return true;
}
Node<T> temp = head;
//在普通节点里面寻找要被删除的元素
while (temp.next != null) {
if (temp.next.element == data) {
temp.next = temp.next.next;
return true;
}
temp = temp.next;
}
return false;
}
//逆置链表 以头插的方式完成
/*
用 temp 指向链表中第一个数的引用,从第一个数开始挪
temp = head.next; head.next = null;
将 temp 的后面的数即链表中第二个数先保存 tempNext = temp.next
此时链表分为头节点、链表中第一个数、第二个数及其后的数
此时 temp 已经孤立,可以对其操作
把 temp 以头插的方式插入到链表中
以此类推...
*/
public void reverseMineLikeInsertHead() {
//空链表则返回
if (head.next == null) return;
Node<T> temp = head.next;
head.next = null;
while (temp != null) {
Node<T> tempNext = temp.next;
temp.next = head.next;
head.next = temp;
temp = tempNext;
}
}
//得到指定位置的节点元素 下标:0 1 2 3 4
public T getIndexValue(int index) {
//链表为空则返回
if (head.next == null || index < 0) return null;
Node<T> temp = head;
while (temp.next != null && index-- > 0) {
temp = temp.next;
}
if (index <= 0) {
return temp.next.element;
}
//下标:0, 1, 2, 3
return null;
//return temp.element, 下标:1, 2, 3, 4
}
//得到链表长度
public int getLength() {
//链表为空则返回
if (head.next == null) return -1;
int size = 0;
Node<T> temp = head;
while (temp.next != null) {
size++;
temp = temp.next;
}
return size;
}
}
四、不带头结点链表
class LinkedListWithoutHead<T extends Comparable<T>> {
class Node<E> {
public E element;
public Node<E> next;
public Node(E data) {
this.element = data;
}
}
//head 标记着链表的第一个元素
public Node<T> head;
public LinkedListWithoutHead() {
head = null;
}
//头插
public void addHead(T data) {
//第一次创建的时候,newNode 是第一个节点, head是链表中第一个节点的标记
//第二次
Node<T> newNode = new Node<>(data);
//当前链表中已有节点,让 head 只作为链表中第一个元素的标记
if (head != null) {
//此时 newNode 是第二个节点,它的 next 域是链表中第一个节点,这个节点用 head 标记
newNode.next = head;//把 head 的引用赋给第二个节点的 next 域
//以此类推...
}
//当前链表中没有节点,让 head 做标记
//已有,更新 head
head = newNode;
}
//尾插
public void addTail(T data) {
Node<T> newNode = new Node<>(data);
//仍然判断链表是否为空
if (head != null) {
//head 是链表中第一个元素的标记,不能随便搞
Node<T> temp = head;
//遍历链表,找到链表中最后一个元素
while (temp.next != null) {
temp = temp.next;
}
temp.next = newNode;
//有 return 语句不用 else 语句
// return;
} else {//链表为空,让 head 做链表第一个元素的标记
head = newNode;
}
}
//删除
public boolean delete(T value) {
if (head == null) return false;
if (value == head.element) {
head = head.next;
return true;
}
Node<T> temp = head;
while (temp.next != null) {
if (value == temp.next.element) {
temp.next = temp.next.next;
}
temp = temp.next;
}
return false;
}
//在指定位置添加一个节点
public boolean addInGivenPosition(T data, int pos) {
if (pos < 0 || pos > getLength() + 1 || head == null) return false;
Node<T> newNode = new Node<>(data);
if (pos == 0) {
newNode.next = head;
head = newNode;
return true;
}
Node<T> temp = head;
//大于 1 可以添加在指定下标
while (temp.next != null && pos-- > 1) {
temp = temp.next;
}
Node<T> tempNode = temp.next;
temp.next = newNode;
newNode.next = tempNode;
return true;
}
//以头插的方式逆置链表
public void reverseLikeInsertHead() {
//链表为空或只有一个节点,不用逆置
if (head == null || head.next == null) return;
if (head.next != null) {
Node<T> temp = head.next;
head.next = null;
while (temp != null) {
Node<T> tempNext = temp.next;
temp.next = head;
head = temp;
temp = tempNext;
}
}
}
//打印链表 toString
public String toString() {
StringBuilder sbr = new StringBuilder();
//此时head 指向链表中第一个元素
Node<T> temp = head;
while (temp != null) {
sbr.append(temp.element + " ");
temp = temp.next;
}
return sbr.toString();
}
//打印链表 循环
public void showLink() {
Node<T> temp = head;
while (temp != null) {
System.out.print(temp.element + " ");
temp = temp.next;
}
System.out.println();
}
//链表长度
public int getLength() {
int size = 0;
Node<T> temp = head;
while (temp != null) {
size++;
temp = temp.next;
}
return size;
}
//返回指定节点
public T getIndexValue(int index) {
if (head == null || index < 0) return null;
Node<T> temp = head;
while (temp != null && index-- > 0) {
temp = temp.next;
}
if (index <= 0) {
return temp.element;
}
return null;
}
//返回链表的头
public Node<T> getHead() {
return head;
}
//设置链表的头
public void setHead(Node<T> head) {
this.head = head;
}
//合并两个有序列表
public Node<T> mergeMyLink(Node<T> head1, Node<T> head2) {
if (head1 == null) return head2;
if (head2 == null) return head1;
Node<T> curHead;
if (head1.element.compareTo(head2.element) < 0) {
curHead = head1;
head1 = head1.next;
} else {
curHead = head2;
head2 = head2.next;
}
Node<T> temp = curHead;
while (head1 != null && head2 != null) {
if (head1.element.compareTo(head2.element) <= 0) {
temp.next = head1;
head1 = head1.next;
temp = temp.next;
} else {
temp.next = head2;
head2 = head2.next;
temp = temp.next;
}
}
if (head1 == null) {
temp.next = head2;
} else if (head2 == null) {
temp.next = head1;
}
head = curHead;
return head;
}
//逆序输出单链表
public <T> void reversePrintList(Node<T> head){
//递归终止条件
if(head == null){
return;//处理办法
}
//提取重复逻辑,缩小问题规模
reversePrintList(head.next);
System.out.print(head.element + " ");
}
//查找单链表中倒数第K个元素
public T findReverseValue(LinkedListWithoutHead<T> list,int k){
if (k <= 0 || k > list.getLength()) return null;
//倒数第 k 个不就是 list.getLength() - k
int index = list.getLength() - k;
Node<T> temp = head;
while (temp != null && index-- >0){
temp = temp.next;
}
if (index <= 0){
return temp.element;
}
return null;
}
//查找链表倒数第k个元素 高效算法
public Node<T> findElem(Node<T> head, int k) {
if(k < 1 || head == null) {
return null;
}
Node<T> front = head;
Node<T> behind = head;
/*
搞一个快引用,一个慢引用
让快引用先走它个 k-1 米
当它还可以继续给后走
快、慢一起做运动
当快引用走到最后一个时
慢引用恰好走到倒数第 k 个
努力运动、好好 keep 哦
*/
for(int i=0; i<k-1; i++){
if(front.next != null){
front = front.next;
}else{
return null;
}
}
while(front.next != null){
front = front.next;
behind = behind.next;
}
return behind;
}
//不允许遍历链表,在 pos 前插入 data
/*
先把新节点放到 pos 后,
把 pos.element 赋值给新的节点
再把 data 赋值给 pos.element
偷天换日,假装插到了 pos 前
*/
public void NoCycleInsertBeforePos(LinkedListWithoutHead<T> list, Node<T> pos, T data){
if (list.head == null || pos == null) return;
Node<T> newNode = new Node<>(data);
newNode.next = pos.next;
pos.next = newNode;
newNode.element = pos.element;
pos.element = data;
}
}