链式储存结构:逻辑上连续;物理上不一定连续;比如老师点名确定人数情况;2号同学在一号后面;但是他两并不需要按照顺序坐
链表
前面顺序表缺陷
1:当我需要放101个元素;扩容扩到150不就浪费49个空间吗
2:每次删除和插入都要移动很多元素。所以就不适合用于频繁插入好删除的场景;查找比较适合;通过下标可以达到O(1)
链表:随去随用;插入删除不需要移动元素。链表有八种结构。
单向链表:
带头节点:第一个节点的数据是没意义的,永远标志这个链表的头。头插也是插在这个后面。
不带头节点:头插一次就变新的头节点
循环:
单向循环不带头:最后一个null变成头节点的地址;头是会在头插时改变;头的值是有效的
单向循环带头:最后一个null变成指向头节点的地址;
实现单向不带头链表
结构分析:如果我们使用类表示链表;那么里面的节点使用内部类的方式表示就很适合。:
先是一个类表示链表;然后里面一个内部类(有val;next;提供一个初始化的构造方法;我们可以指定值添加节点);外面是一个头指针。
class MyLink{
class Node{
public int val;
//节点类型正好用来存放下一个节点;现在是对象代表节点
public Node next;
public Node(int val) {
this.val = val;
}
}
Node head=null;
}
1:提供一个初始化方法;一调用;马上给你创建5个节点
//初始化方法;我在想我们在main方法怎么初始化呢;死去的回忆在攻击我;内部类的实例化
public void createList(){
Node node1=new Node(25);
Node node2=new Node(250);
Node node3=new Node(2500);
Node node4=new Node(25000);
Node node5=new Node(250000);
//还需要绑定节点
node1.next=node2;
node2.next=node3;
node3.next=node4;
node4.next=node5;
head=node1;
}
2:打印的方法
public void print(){
Node cur=head;
// 使用cur是因为头指针可不能走
// 打印链表; 这里遍历是注意是使用cur.next==null还是cur==null
//想想如果是cur.next;那么最后一个val就打印不了;循环进不去。还有问题就是空的时候没有cur.next
while (cur!=null){
System.out.println(cur.val);
cur=cur.next;
}
}
3:获取链表长度方法
// 获取链表长度;
public int Size(){
Node cur=head;
int count=0;
while (cur!=null){
count++;
cur=cur.next;
}
return count;
}
4:查找是否包含关键字k
// 获取链表长度;
public boolean contains(int key){
Node cur=head;
while (cur!=null){
if(cur.val==key){
// 如果是引用类型就不能用==;字符串得用equles方法。
return true;
}
cur=cur.next;
}
return false;
}
5:头插和尾插法
画图分析:
第二种方案:遇到空链表的情况也是适用的。因为head就是null了。而第一种的head.next就空指针异常。
技巧:链表插入数据时;一定得先绑定后面的节点。再绑定前面的节点。
// 头插法;我们无头;所以头插是直接放到头去;
// 头插需要注意什么细节。如果没有元素?
// public void head(Node N){
//直接传节点有点不友好;可以选择传个数进来;你要头插这个数;我们在里面给创建成节点然后插入就好了
public void addFirst(int data){
Node N=new Node(data);
if(Size()==0){
head=N;
head.next=null;
return;
}
N.next=head;
head=N;
}
// 尾插法
// 需要注意什么细节;如果没有元素;怎么找到最后个元素呢
public void addLast(int data) {
Node N=new Node(data);
Node cur=head;
// 让cur去往下遍历; 直到这个就是我们要找的最后个元素
if(Size()==0){
//或者这里写head==null;cur==null更高效。
head=N;
head.next=null;
return;
}
//找位置;尾插找到位置只需要把原先最后节点的next改成N的节点就好了;N的next本来就是null的
//怎么找;按之前的遍历方法行不行呢;不行的;因为这样子走过了;走到最后一个节点;再进行这一次的循环执行cur=cur.next;cur就变成空了。
//所以我们得让它在倒数的上一次循环执行就应该出来了;经过执行cur=cur.next;就恰好是我们想要的最后位置。所以这里得用cur.next!=null;
//while(cur!=null){
// cur=cur.next;
//}
while(cur.next!=null){
cur=cur.next;
}
N.next=null;
cur.next=N;
}
6:插到中间位置
//插入到中间节点;以第一个节点为0下标;插入在index下标
// 有什么细节呢;如果是没有元素;如果是只有一个元素;头插尾插
// index如果是负数呢。如果是0和链表长度那就调用上述的头插和尾插方法。然后就找位置;找到再进行普通的插入
// 还有不能隔着插入;比如只有5个元素;你要插入到10位置
public void add(int index,int data){
Node N=new Node(data);
if(index<0||index>Size()){
System.out.println("位置不合法");
return;
}
// data等于0就包括没有元素的插入;data==Size()尾插的情况
if(index==0){
addFirst(data);
}
if(index==Size()){
addLast(data);
}
//走到这里就是真正的中间插入;先找位置。使用index计数找位置;之前找尾节点也能用这种方法
//或者倒着走更好;不用创建count变量;走index-1步就找到cur。
// 条件(index-1 !=0) cur=cur.next; index--;
int count=0;
Node cur=head;
while (count<index){
cur=cur.next;
count++;
}
// cur找到位置;注意先绑定后面节点;画图就能很好看出;哪个地方要改成哪里的值
N.next=cur.next;
cur.next=N;
}
7:删除val的方法
// 删除值为key的元素;是删除出现的第一个吗 是的
// 删除有什么细节呢;空的时候没法删除;如果删除的是头节点(head往后移即可);尾节点。只有一个元素的情况是属于删除尾节点。
public Node remove(int key){
if(Size()==0){
System.out.println("没元素可以删除");
return null;
}
// 删除头节点
if(head.val==key){
head=head.next;
}
// 删除尾节点;那也得找尾节点;
// 删除只是跳过一个节点;假设cur是删除的前一个节点;
// 让cur.next=cur.next.next.当是尾节点cur.next.next就是null;这种情况也是符合普通情况
Node cur= selectago(key);
if(cur==null){
System.out.println("没有这个元素");
return null;
}
//以删除节点为基准;前面一个节点的next等于;下一个节点的地址;因为下一个节点的地址就是这个删除节点的next。
cur.next=cur.next.next;
return cur;
}
private Node selectago(int key) {
// 找删除的前一个节点
Node cur=head;
while (cur!=null){
if(cur.val==key){
return cur;
}
cur=cur.next;
}
return null;
}
8:删除所有的key
//删除所有的key;想法是什么;注意细节:首先空是不能删;头不能先删;避免删了后面还是要删头
public void removeAllkey(int key){
// 方法1;循环遍历调用刚才的删除方法;但是;时间复杂度太大了。可达到O(n^2)
// 删除头节点和没有元素情况下还是少不了的;
if(Size()==0){
System.out.println("没元素可以删除");
}
//先不考虑头的情况;先删除后面的;因为你比如删除5;第一个是;删完第一个万一第二个还是5呢
//删除得要特别注意;要找到前驱才能删除这个节点;cur从头开始;cur等于head.next就不好搞了;还需要记录一下前驱是什么。
Node cur=head;
while (cur.next!=null){
if(cur.next.val==key) {
cur.next = cur.next.next;
}
else {
cur=cur.next;
}
}
// 最后后面删完再看看头是否要删
if(head.val==key){
head=head.next;
}
}
9:清空方法
// 清空链表的方法
public void clean(){
head=null;
// 标记删除;插入就覆盖进去;并不是到回收内存的那步;
}