链表的定义:
链表是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针
链表的特点:
使用链表结构可以克服数组链表需要预先知道数据大小的缺点
链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理
链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大
单项链表:
双向链表:
循环链表(单向循环链表):
查找单向链表指定倒数索引处的结点:
单向链表的反转:
反向遍历单链表:
第一种方法:
双层for循环实现,时间复杂度:O(n^2)
第二种方法:
利用栈的先进后出特点实现,时间复杂度:O(n)
import java.util.Stack;
class ListNode {
Object data;
ListNode next;
@Override
public String toString() {
return "ListNode{" +
"data=" + data +
", next=" + next +
'}';
}
/**
* 有参构造创建结点
* @param value 结点的值
*/
ListNode(Object value) { data = value; }
}
//单链表
public class SingleLinkedList{
//先初始化一个头结点,不存放数据
private ListNode head = new ListNode(null);
/**
* 在单链表的最后面插入结点
* @param listNode 所以插入的结点
*/
public void appendNode(ListNode listNode){
//定义一个临时变量接收头结点
ListNode temp = head;
while (temp.next!=null){
temp = temp.next;
}
temp.next = listNode;
}
/**
* 遍历链表元素
*/
public void show(){
//定义一个临时变量接收头结点
ListNode temp = head.next;
//判断链表是否为空
if(head.next==null){
System.err.println("链表为空...");
return;
}
while (true){
//若遍历到最后一个结点时,则停掉死循环
if(temp==null){
break;
}
//输出结点信息
System.out.println(temp);
//将temp后移,准备下一次打印信息
temp = temp.next;
}
}
/**
* 删除指定索引处的结点
* @param index 所要删除的结点对应的索引
*/
public void delete(int index){
ListNode temp = head;
if(getLength()<index+1){
System.err.println("该索引处没有结点信息...");
return;
}
for (int i = 0; i < index; i++) {
temp = temp.next;
}
temp.next=temp.next.next;
}
/**
* 获得单链表的有效结点个数
* @return 有效结点个数
*/
public int getLength(){
//定义一个临时变量
ListNode temp = head.next;
//定义一个计数器
int count = 0;
while (true){
if(temp==null){
break;
}else {
count++;
temp = temp.next;
}
}
return count;
}
/**
* 获得指定索引处的结点信息
* @param index 索引
* @return 该索引处的结点信息
*/
public ListNode getNodeByIndex(int index){
//定义一个临时变量
ListNode temp = head;
//index>getLength()-1,就会越界
if(index>getLength()-1){
throw new RuntimeException("此索引处没有结点信息...");
}else {
for (int i = 0; i < index+1; i++) {
temp = temp.next;
}
return temp;
}
}
/**
* 获得倒数指定索引处的结点信息
* @param lastIndex 倒数的索引
* @return 倒数的索引对应的结点信息
*/
public ListNode getNodeByLastIndex(int lastIndex){
//定义一个临时变量
ListNode temp = head;
if(lastIndex>getLength()-1){
throw new RuntimeException("此索引处没有结点信息...");
}else {
for (int i = 0; i < getLength() - lastIndex; i++) {
temp = temp.next;
}
return temp;
}
}
/**
* 将链表反转遍历
*/
public void reverseShow(){
//第一种:时间复杂度:O(n^2)
// for (int i = getLength(); i > 0; i--) {
// ListNode temp = head;
// for (int j = i; j > 0; j--) {
// temp = temp.next;
// }
// System.out.println(temp);
// }
//第二种:利用栈的先进后出特点,时间复杂度:O(n)
ListNode cur = head.next;
Stack<ListNode> stack = new Stack<>();
if(cur==null){
return;
}
//将结点存入到栈中
while (cur!=null){
stack.push(cur);
cur = cur.next;
}
//遍历栈元素
while (stack.size() > 0){
System.out.println(stack.pop());
}
}
/**
* 将链表的结点反转(****腾讯面试题****)
*/
public void reverseNode(){
//定义当前结点,初始值为第一个有效结点
ListNode cur = head.next;
//定义一个辅助头结点
ListNode assNode = new ListNode(null);
//如果单链表没有结点或者只有1个结点,那么反转后还是它本身
if(cur==null||cur.next==null){
return;
}
while (cur!=null){
//定义一个临时变量,用于存放当前结点的下一个结点
ListNode next = cur.next;
//将 cur 结点的 next指针指向新的链表的最前端
cur.next = assNode.next;
//将 cur 链接到新的链表上
assNode.next = cur;
//让cur后移
cur = next;
}
//将原单链表的指针指向新单链表的第一个结点
head.next = assNode.next;
}
}
public class DoubleLinkedList {
//先初始化一个头结点,不存放数据
private DoubleNode head = new DoubleNode(null);
/**
* 添加结点到双向链表的最后
* @param node 所要添加的结点
*/
public void add(DoubleNode node){
//定义一个临时变量
DoubleNode temp = head;
//while循环玩后,temp就会移动到双向链表尾部
while (temp.next!=null){
temp = temp.next;
}
//将最后一个结点的next指针指向所要添加的新结点
temp.next = node;
//将所要添加的结点的pre结点指向原来双向链表的最后一个结点
node.pre = temp;
}
/**
* 删除指定索引处的结点
* @param index 指定索引
*/
public void delByIndex(int index){
if(index>getLength()-1){
System.out.println("该索引处没有结点信息...");
}
//定义一个临时变量
DoubleNode temp = head.next;
//for循环移动temp
for (int i = 0; i < index; i++) {
temp = temp.next;
}
//所要删除结点的上一个结点
DoubleNode preNode = temp.pre;
//所要删除结点的下一个结点
DoubleNode nextNode = temp.next;
//将所要删除结点的上一个结点的next指针指向所要删除结点的下一个结点
preNode.next = nextNode;
if(nextNode!=null){
//将所要删除结点的下一个结点的pre指针指向所要删除结点的上一个结点
nextNode.pre = preNode;
}
}
/**
* 获得指定索引处的结点
* @param index 指定索引
*/
public DoubleNode getByIndex(int index){
if(index>getLength()-1){
throw new RuntimeException("该索引处没有结点信息...");
}
//定义一个临时变量
DoubleNode temp = head;
//for循环将temp向后移位 index+1 次
for (int i = 0; i < index + 1; i++) {
temp = temp.next;
}
return temp;
}
public void insertByIndex(int index,DoubleNode node){
if(index>getLength()){
System.out.println("索引超过了链表长度...");
}
if(index==getLength()){
//如果index==getLength(),那么所要插入的结点就是要插入到双向链表尾部
add(node);
return;
}
//定义一个临时变量
DoubleNode temp = head;
//for 循环移动临时变量 temp
for (int i = 0; i < index + 1; i++) {
temp = temp.next;
}
//当前索引结点的上一个结点
DoubleNode preNode = temp.pre;
//将当前索引结点的上一个结点的next指针指向所要插入的结点
preNode.next = node;
//将所要插入结点的pre指针指向当前索引的上一个结点
node.pre = preNode;
//将所要插入结点的next指针指向当前结点
node.next = temp;
}
/**
* 获得双向链表的有效结点个数
* @return 有效结点个数
*/
public int getLength(){
//初始一个计数器
int count = 0;
//定义一个临时变量
DoubleNode temp = head;
while (temp.next!=null){
count++;
temp = temp.next;
}
return count;
}
/**
* 遍历双向链表信息
*/
public void show(){
//定义一个临时变量
DoubleNode temp = head.next;
while (temp!=null){
System.out.println(temp);
//不断后移temp临时变量
temp = temp.next;
}
}
}
class DoubleNode{
//用于存放结点的数据
Object data;
//用于指向前一个结点的指针
DoubleNode pre;
//用于指向下一个结点的指针
DoubleNode next;
public DoubleNode(Object data) {
this.data = data;
}
@Override
public String toString() {
return "DoubleNode{" +
"data=" + data +
'}';
}
}
public class CircularLinkedList {
//初始化第一个结点,默认值为null,一会添加第一个结点的时候会修改它
private CircularNode head = new CircularNode(null);
/**
* 在循环链表的尾部添加结点
* @param node 所要添加的结点
*/
public void add(CircularNode node){
//判断循环链表是否为空
if(head.next == null){
head = node;
//让它变成一个环形的
head.next = node;
return;
}
//特别注意添加第二个结点的时候
if(getSize()==1){
head.next = node;
node.next = head;
}
//定义一个临时变量
CircularNode temp = head;
while (temp.next != head){
temp = temp.next;
}
//让尾部的结点的 next 指针指向新结点
temp.next = node;
//让新结点的 next 指针指向第一个结点
node.next = head;
}
/**
* 删除指定索引处的结点信息
* @param index 指定索引
*/
public void delByIndex(int index){
if (index>getSize()-1||index<0){
System.out.println("索引越界...");
return;
}
//特殊情况是链表只有一个结点,而我恰巧要删这个仅有的结点
if(getSize()==1){
head = new CircularNode(null);
return;
}
if(index==0){
CircularNode next = head.next;
head = next;
//此时原来的第一个结点被移到了最后,所以原来链表的最后一个结点会往前挪一位
getByIndex(getSize()-2).next = next;
return;
}
//定义一个临时变量
CircularNode temp = head;
for (int i = 0; i < index-1; i++) {
temp = temp.next;
}
temp.next = temp.next.next;
}
/**
* 获得循环链表的结点个数
* @return 结点个数
*/
public int getSize(){
//循环链表为空时
if(head.next == null){
return 0;
}
//只有一个结点时
if(head.next == head){
return 1;
}
int count = 1;
CircularNode temp = head;
while (temp.next != head){
temp = temp.next;
count++;
}
return count;
}
/**
* 从头到尾打印循环链表的所有结点的data值
*/
public void show(){
CircularNode temp = head;
//循环链表为空
if(temp.next == null){
System.out.println("循环链表为空,无结点可打印...");
return;
}
System.out.println(head.data);
while (temp.next != head){
temp = temp.next;
System.out.println(temp.data);
}
}
/**
* 获得指定索引处的结点信息
* @param index 指定索引
* @return 指定索引处的结点信息
*/
public CircularNode getByIndex(int index){
if(index>getSize()-1||index<0){
throw new RuntimeException("索引越界...");
}
//定义一个临时变量
CircularNode temp = head;
for (int i = 0; i < index; i++) {
temp = temp.next;
}
return temp;
}
}
class CircularNode{
Object data;
CircularNode next;
public CircularNode(Object data) {
this.data = data;
}
public Object getData() {
return data;
}
public CircularNode getNext() {
return next;
}
}