顺序表的查询很快,但是插入和删除性能不行,一方面有元素移动的问题,另一方面也会有扩容缩容的问题。
那么为解决这个问题,就有了一个新的线性结构- 链表:链表是概念上、逻辑上的连续结构,在物理上链表是非连续、非顺序的。
- 链表的最大优势就在于插入和删除的性能非常高。
- 链表的问题在于根据地址获取数据,性能高,需要逐步寻址。
- 链表不是通过数组实现的,而是通过节点实现的 。
/**
*节点类是 MySingleLinkedList<T>类的内部类。
*/
//节点类设计
public class Node{
T item;//节点存储的数据
Node next;//下一个节点的地址
public Node(T item,Node next){
this.item=item;
this.next=next;
}
}
1.单向链表的实现:
单向链表是指每个节点只有一个数据变量和一个地址变量组成,数据变量用于存储当前节点的数据,地址变量用于存储下一个节点的地址。
单向链表类的设计:public class MySingleLinkedList<T> {
//成员变量
Node head;//存储链表的起始位置
int size;//记录链表中的元素数量
//构造方法
public MySingleLinkedList();//成员变量 Node head;//存储链表的起始位置(头部节点不存储数据) int size;//记录链表中的元素数量 //构造方法 public MySingleLinkedLists(){ //将头部的节点进行初始化 this.head=new Node(null,null); //将size初始化 this.size=0; }
//常用方法
public int size();//获取当前存储元素的数量//常用方法 //获取当前存储元素的数量 public int size(){ return this.size; }
public T get(int pos);//获取指定位置的元素//获取指定位置的元素 public T get(int pos){ return getNode(pos).item; } //获取指定位置的节点 public Node getNode(int pos){ //在指定位置加入元素时,是0位置时 if (pos==-1){ return this.head; } //获取0位置的元素 Node target=this.head.next; //通过遍历找到指定位置的元素。 for (int i = 0; i < pos; i++) { target=target.next; } return target; }
public void add(T t);//在尾部添加元素//在尾部添加元素 public void add(T t){ //创建插入的节点 Node node=new Node(t,null); if (this.size==0){ //插入head的next this.head.next=node; }else { //找到最后一个节点 this.getNode(size-1).next=node; } //累计的数量加1 this.size++; }
public void add(int pos,T t); //在指定位置添加元素
//在指定的位置添加元素 public void add(int pos,T t){ //获取需要插入的节点 Node node=new Node(t,null); //根据pos获取插入点的节点 Node current=this.getNode(pos); //获取pos前面位置的节点 Node before=this.getNode(pos-1); //将before的next节点指向需要插入的节点 before.next=node; //将插入节点的next指向当前pos上的节点 node.next=current; //累计加1 this.size++; }
public T remove(int pos); //删除指定位置的元素//删除指定位置的元素 public T remove(int pos){ //获取pos前面位置的节点 Node before=this.getNode(pos-1); //根据pos获取需要删除的节点 Node current=this.getNode(pos); //将before的next节点指向需要删除的节点的next before.next=current.next; //累计减1 this.size--; return current.item; }
测试类:
package ListStructure;
public class MyTest {
public static void main(String[] args) {
MySingleLinkedLists<String> list=new MySingleLinkedLists<>();
for (int i = 0; i < 10; i++) {
//在末尾加入数据
list.add("我是第"+(i+1)+"个加入数据");
}
list.add(0,"我是插入的数据");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
System.out.println("我是获取的数据:"+list.get(5));
System.out.println("我是链表的size:"+list.size());
String rem=list.remove(2);
System.out.println("我被删了:"+rem);
}
}
2.单向链表的反转:
方法1:利用一个临时的反转头实现
1.先创建一个临时的反转头节点(已经反转的链表的起点)
2.从现有的头节点中取出next节点(0号节点),然后将现有的头节点指向next的next节点(0号节点的下一个节点)
3.将取出来的0号节点的next指向反转头节点的next节点
4.并且将反转头的next节点指向0号节点(相当于将正序的链表中0号节点,插入到已经反转的链表的0号节点处)
如此反复直到正序链表头的next指向空
//单向链表的反转
public void reverse(){
//创建临时的反转头节点。
Node newHead=new Node(null,null);
//循环取出0号节点
while (this.head.next!=null) {
//获取头节点的下一个节点。
Node first = this.head.next;
//将原来的头节点指向下一个节点的下一个。
this.head.next = this.head.next.next;
//将取出的节点的next指向反转头的next
first.next = newHead.next;
//将反转头的节点指向取出的节点。
newHead.next = first;
}
//将原头节点指向反转头节点的next
this.head.next=newHead.next;
}
方法2:利用递归实现
递归反转就是从原链表的第一个存储节点开始,依次递归调用反转每一个节点,直到把最后一个反转完毕,整个链表就反转完毕了。
//递归实现反转
public Node reverse(Node curr){
//判断是否存在下一个节点
if (curr.next!=null){
//通过反转下一个节点,获得prev
Node prev=reverse(curr.next);
//将之前的节点的next节点设置成当前的节点
prev.next=curr;
//返回curr节点
return curr;
}else {
//当前节点没有下一个节点
//将头部节点的next指向当前节点
this.head.next=curr;
return curr;
}
}
public void reverse2(){
this.reverse(this.head.next);
}
3.快慢指针:
快慢指针即在链表遍历的时候设计两个指针,一个移动的快一些,一个移动慢一些,通过这样的方式提高一些解决问题的效率
1.查找中间值:
//快慢指针得到链表的中间值
public T getMiditem(){
//定义快慢指针
Node fast=this.head.next;
Node slow=this.head.next;
//快指针是慢指针速度的两倍,这样快指针到最后时,慢指针刚好在中间。
while (fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
}
//fast走完,slow刚好在中间
return slow.item;
}
2.输出链表中倒数第K个节点:
//获取倒数第k个节点的元素
public T getRKItem(int pos){
//定义快慢指针
Node fast=this.head.next;
Node slow=this.head.next;
//先让指针走pos-1步
int before=pos-1;
for (int i = 0; i < before; i++) {
fast=fast.next;
}
//然后快慢指针都走1步每次
while (fast.next!=null){
fast=fast.next;
slow=slow.next;
}
return slow.item;
}
4.循环链表:
1.使用快慢指针解决链表是否是循环链表的问题:
//是否是循环链表
public boolean hasCircle(){
//定义快慢指针
Node fast=this.head.next;
Node slow=this.head.next;
//快指针是慢指针速度的两倍
while (fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
//当快慢指针可以重合时,就是循环链表
if (fast!=null&&fast.equals(slow)){
return true;
}
}
//指针可以遍历到null值表示没有循环
return false;
}
2.有环链表的入口:
当快慢指针相遇时,我们可以判断链表中有环,这时重新设定一个新指针指向链表的初始位置,且步长与慢指针一样为1,则慢指针与“新”指针相遇的地方就是环的入口
//找到有环链表的入口。
public T getCircleEntry(){
//定义快慢指针
Node fast=this.head.next;
Node slow=this.head.next;
//快指针是慢指针的两倍速
while (fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
//快指针与慢指针重合,表示有环
if (fast!=null&&fast.equals(slow)){
Node newPoint=this.head.next;
//移动新指针和慢指针直到两指针重合
while (!slow.equals(newPoint)){
slow=slow.next;
newPoint=newPoint.next;
}
//环的入口元素
return newPoint.item;
}
}
return null;
}
测试类:
package ListStructure;
public class MyTest {
public static void main(String[] args) {
MySingleLinkedLists<String> list=new MySingleLinkedLists<>();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
list.add("ee");
list.add("ff");
list.add("gg");
for (int i = 0; i < list.size; i++) {
System.out.println(list.get(i));
}
System.out.println("这是环状链表吗?"+list.hasCircle());
System.out.println("倒数第3个元素是:"+list.getRKItem(3));
System.out.println("中间值是"+list.getMiditem());
//在链表中建立一个环
MySingleLinkedLists.Node node2=list.getNode(2);
MySingleLinkedLists.Node nodeList=list.getNode(list.size-1);
nodeList.next=node2;
System.out.println("这是循环链表吗?"+list.hasCircle());
String result=list.getCircleEntry();
System.out.println("循环链表的入口是:"+result);
}
}
5.约瑟夫环:
//约瑟夫环的实现
//sum是总数
//num是间隔数
public void jsfKill(int sum,int num){
//创建sum个节点
for (int i = 1; i <=sum; i++) {
this.add((T) (i+""));
}
//找到最后一个链表元素。
Node last=this.getNode(this.size-1);
//将最后一个元素的next设置给第一个元素,形成环状。
last.next=this.head.next;
//定义一个指针
Node target=this.head.next;
//定义一个计数器
int count=1;
while (target.next!=target){
//将前一个元素进行保存
Node prev=target;
//指针每次移动一格,计数器就加1
target=target.next;
count++;
if (count==num){
System.out.println("删除了的节点是:"+target.item);
//重置计数器
count=1;
//指针移动到下一个节点
target=target.next;
//删除目标值后,链接节点
prev.next=target;
}
}
System.out.println("最后剩下的节点是:"+target.item);
}
测试类
package ListStructure;
public class MyTest2 {
public static void main(String[] args) {
MySingleLinkedLists<String> lists=new MySingleLinkedLists<>();
lists.jsfKill(41,3);
}
}
6.双向链式结构:
1.自己做的双向链表
package ListStructure;
public class MyDoubleLinkedList<T> {
//节点类
public class Node{
//存储数据的变量
T item;
//指向上一个节点的引用
Node prev;
//指向下一个节点的引用
Node next;
public Node (Node prev,T item,Node next){
this.item=item;
this.next=next;
this.prev=prev;
}
}
//声明开始节点
Node first;
//声明最后的节点
Node last;
//定义链表内容的长度
int size;
//构造方法
public MyDoubleLinkedList(){
this.size=0;
}
//获取当前存储元素的数量
public int size(){
return this.size;
}
//在尾部添加元素
public void add(T t){
//将last存储在prev节点
Node prev=last;
//创建需要插入的节点
Node node=new Node(prev,t,null);
//加入的元素为第一个元素时
if (prev==null){
//将first指针指向当前元素
first=node;
}else {
//将prev节点的next指向新插入的节点
prev.next=node;
}
//将last指向新插入的节点
last=node;
//数量增加
this.size++;
}
//根据位置获取节点的方法
public Node getNode(int pos){
if (first==null){
return null;
}
//定义中间的变量
int middle=this.size/2;
if (pos<middle){
//从前面开始遍历
Node x=first;
for (int i = 0; i <pos; i++) {
x=x.next;
}
return x;
}else{
Node x=last;
//从后面开始遍历
for (int i = size-1; i > pos; i--) {
x=x.prev;
}
return x;
}
}
//在指定位置插入节点
public void add(int pos,T t){
if (pos==this.size){
//直接在尾部添加
add(t);
}else {
//创建新节点
Node node=new Node(null,t,null);
//获取目标的位置
Node target=getNode(pos);
//获取位置的节点就为空
if (target==null){
//没有元素时
first=node;
}else {
//获取目标前一个的位置
Node prev=target.prev;
//指定新节点的prev到目标的前一个
node.prev=prev;
//新节点的next到目标。
node.next=target;
//目标节点的prev到新节点
target.prev=node;
if (prev==null){
//first指向插入的元素
first=node;
}else {
//指定目标的前一个的next到新节点
prev.next=node;
}
}
this.size++;
}
}
//指定位置的删除
public void remove(int pos){
Node target=this.getNode(pos);
Node prev=target.prev;
Node next=target.next;
if (prev==null){
first=next;
}else {
prev.next=next;
}
if (next==null){
last=prev;
}else {
next.prev=prev;
}
this.size--;
}
}
2.测试类
package ListStructure;
public class Test12 {
public static void main(String[] args) {
MyDoubleLinkedList<String> list=new MyDoubleLinkedList<>();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
list.add("ee");
list.add("ff");
list.add(0,"在第一个位置插入的元素");
list.add(4,"我是随便");
list.add(list.size(),"我是最后一个元素的插入");
list.remove(list.size()-1);
for (int i = 0; i < list.size(); i++) {
System.out.println("获得的元素是"+list.getNode(i).item);
}
}
}