数据结构是一种特殊的组织和存储数据的方式,可以使我们可以更高效地对存储的数据执行操作。
一、链表(单向链表)
链表是一种物理存储结构非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的。
链表的分类
- 单向链表,双向链表
- 带头链表,不带头链表
- 循环的,非循环的
排列组合后一共有
即一共8种链表,其中单向、不带头、非循环以及双向、不带头、非循环的链表最为重要,也是本文主要介绍的链表类型。
链表的结构
一个链表 头节点 为head
head 1 2 3 4 5 6
head叫做链表的头节点
1所在的节点叫做链表的首节点
从定义上严格来说头节点head本身并没有值,它只是一个指向首节点1的指针。也就是说head.val为空,head.next.val=1。即head的下一个节点才是1的节点。那上述的链表就有7个节点(包含头节点head)。
但是,在一些编程题里,有可能把 头节点 默认为 首节点。也就是说head不仅指向1,而且head.val=1,那head.next.val=2。即head就是1的节点,head的下一个节点是2的节点。那么这个链表一个就有6个节点(head和1所在的节点看作一个节点)。head里面的地址就是0x12。 head.next里面存储的地址才是下一个节点的地址0x13。
1(head) 2 3 4 5 6
用代码实现单向链表
1.初始化一个链表
//ListNode代表一个节点
class ListNode{
public int val;
public ListNode next;
//构造函数
public ListNode(int a){
this.val = a;
}
}
2.遍历数据(val)
为了使head一直存在且有意义,我们在display()函数中定义一个cur:ListNode cur = this.head;来替代head。
对于head的移动,可用head = head.next来实现。
public void display(){
ListNode cur = this.head;
while(cur != null){
System.out.print(cur.val+" ");
cur = cur.next;
}
System.out.println();
}
3.查找某个数据是否在链表中
public boolean contains(int key){
ListNode cur = this.head;
while(cur != null){
if(cur.val == key){
return true;
}
cur = cur.next;
}
return false;
}
4.求单链表的长度
public int Size(){
int count = 0;
ListNode cur = this.head;
while(cur != null){
count++;
cur = cur.next;
}
return count;
}
5.链表的头插法(我们这里讲的头插法是不带头链表)
什么叫做头插法:
一个新节点,要插到第一个节点前面:把14这个节点插到最前面去,next是下一个结点的地址。head 要改变成为新的node。
上面所说可以将代码这样写:
node.next =head;
head = node;
这样第一个14的next域就是下一个元素了
然后在把head引用node;
但是我们不可以这样写
head = node;
node.next =head;
把他们顺序颠倒
这样变成自己引用自己了。
class NODE { //构造一个节点的类
public int data;
public NODE next;
public NODE(int data) {
this.data = data;
this.next = null;
}
}
public class LinkedList {
public NODE head; //保存单链表的头节点的引用 代表的是整个链表的头 所以定义在这个地方
//头插法
public void addFirst(int data) {
NODE node = new NODE(data);
if(this.head == null) {
this.head = node;
return;
}
node.next = this.head;
this.head = node;
}
//display 打印该链表
public void display() {
if(this.head == null) {
System.out.println("The List is empty.");
return;
}
NODE cur = this.head;
while(cur != null) {
System.out.print(cur.data + " ");
cur = cur.next;
}
System.out.println();
}
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
linkedList.addFirst(12); //进行头插
linkedList.addFirst(13);
linkedList.addFirst(14);
linkedList.addFirst(15);
linkedList.addFirst(16);
linkedList.display();
}
}
第一次尾插一定要判断链表是否为空值
public void addLast(int data){
//尾插法
Node node = new Node(data); //新节点
if (this.head ==null){
//如果是head是null 那么是第一次 直接 head = node
this.head = node;
}else{
Node cur = this.head; //定义一个cur 代替当做head
while (cur.next!=null){
//找到最后一个节点
cur = cur.next; //如果不是最后节点 一直往后走
}
cur.next =node; //当前节点的next 域指向 新节点
}
}
7.任意位置插入数据
我们把一个值为1314,地址是0x520(设为node引用)的节点,即val域值为1314,next域为null,地址是520,将该节点插入至3号位置。
note.next =cur.next;
cur.next=node;
代码实现:
class NODE { //构造一个节点的类
public int data;
public NODE next;
public NODE(int data) {
this.data = data;
this.next = null;
}
}
public class LinkedList {
public NODE head; //保存单链表的头节点的引用 代表的是整个链表的头 所以定义在这个地方
//查找index的前驱位置
public NODE searchIndex(int index) {
if(index < 0 || index > this.size()) {
throw new RuntimeException("index's location is not right.");
}
NODE prev = this.head;
//NODE cur = this.head.next;
while(index > 1) {
prev = prev.next;
index--;
}
return prev;
}
//任意位置插入data
public void addIndex(int index, int data) {
NODE node = new NODE(data);
if(this.head == null) {
this.head = node;
return;
}
NODE prev = searchIndex(index);
//NODE cur = prev.next;
node.next = prev.next;
prev.next = node;
}
//display 打印该链表
public void display() {
if(this.head == null) {
System.out.println("The List is empty.");
return;
}
NODE cur = this.head;
while(cur != null) {
System.out.print(cur.data + " ");
cur = cur.next;
}
System.out.println();
}
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
linkedList.addIndex(0,90); //index位置插入data数据
linkedList.addIndex(1,91);
linkedList.addIndex(2,92);
linkedList.addIndex(3,93);
linkedList.addIndex(4,94);
linkedList.addIndex(5,95);
linkedList.addIndex(6,96);
linkedList.addIndex(6,100);
linkedList.addIndex(6,123);
linkedList.addIndex(6,234);
linkedList.display();
}
}
8.删除第一次出现的数据的节点
public ListNode searchPerv(int key){
ListNode cur = this.head;
while (cur.next !=null){
if(cur.next.val==key){
return cur;
}
cur =cur.next;
}
return null;
}
public void remove(int key){
if(this.head==null){
System.out.println("单链表为空,不能删除");
return;
}
if(this.head.val==key){
this.head=this.head.next;
return;
}
ListNode cur =searchPerv(key);
if(cur==null){
System.out.println("没有你要删除的节点");
return;
}
ListNode del =cur.next;
cur.next = del.next;
}
总结
链表的优缺点
优点:在插入和删除操作时,只需要修改被删节点上一节点的链接地址,不需要移动元素,从而改进了在顺序存储结构中的插入和删除操作需要移动大量元素的缺点。
缺点:
1、没有解决连续存储分配带来的表长难以确定的问题。
2、失去了顺序存储结构随机存取的特性。