学习笔记:Java中的数据结构链表
一、链表
数组作为一种数据存储结构具有一定的缺陷,在无序数组中,查找是低效的,在有序数组中,插入效率很低,不管哪种数组删除效率都很低,并且数组创建后大小不可以改变。
链表机制灵活,可以取代数组,成为其它存储结构的基础。
链节点
链节点是某个类的对象,
每个链表中有许多类似的链节点,
每个连接点包含数据项和一个对下一个链节点的引用。
public class ListNode {
int data;//数据项
ListNode next;//引用
public ListNode() {
}
public ListNode(int data) {
this.data = data;
}
public ListNode(int data, ListNode next) {
this.data = data;
this.next = next;
}
@Override
public String toString() {
return " "+data ;
}
}
在数组中,可以通过下标号直接访问数组中的元素。
在链表中,寻找一个元素的唯一方法是沿着这个元素的链一直向下访问
二、单链表
单链表的由单链节点实现,只有一个记录首节点的属性;创建一个单链表,没有数据项的情况下,首节点初始化值是null.
public class ListNode {
int data;//数据项
ListNode next;//引用
public ListNode() {
}
public ListNode(int data) {
this.data = data;
}
public ListNode(int data, ListNode next) {
this.data = data;
this.next = next;
}
@Override
public String toString() {
return " "+data ;
}
}
class LinkList {
ListNode first;//维护链表头
public LinkList() {
//链表创建时没有数据项
this.first = null;
}
public boolean isEmpty() {
return this.first == null;
}
//头插法
public void insertFirst(int data) {
ListNode newNode = new ListNode(data);
newNode.next = first;
first = newNode;
}
//删除第一个节点
public void deleteFirst() {
if (!this.isEmpty()) {
first = first.next;
}
}
//查找给定元素的索引
public int indexFind(int data) {
//声明一个指针
ListNode cur = first;
int count = 0;
while (cur != null) {
if (cur.data == data) {
return count;
} else {
cur = cur.next;
count++;
}
}
return -1;
}
//删除指定的元素,有两个或多个相同的元素删除索引最小的,f返回索引
public int deletNode(int data) {
//声明一个指针
ListNode cur = first;
//声明一个指针,记录单链表的前一个元素
ListNode prev = null;
int count = 0;
while (cur != null) {
if (cur.data == data) { //找到位置
if (prev == null) { //删除第一个元素
first = first.next;
break;
} else { //删除其它位置元素
prev.next = cur.next;
break;
}
} else {
prev = cur; //记录前一个元素位置
cur = cur.next;
count++;
}
}
return count;
}
//遍历
public void dispaly() {
//声明一个指针
ListNode cur = first;
System.out.println("链表节点:");
while (cur != null) {
System.out.println(cur.toString());
cur = cur.next;
}
}
}
测试代码:
@Test
public void testListNode() {
LinkList linkList = new LinkList();
//插入节点
linkList.insertFirst(5);
linkList.insertFirst(4);
linkList.insertFirst(3);
linkList.insertFirst(2);
linkList.dispaly();
//删除头节点
linkList.deleteFirst();
linkList.deleteFirst();
linkList.dispaly();
//查找指定的元素
System.out.println("元素索引: "+linkList.indexFind(5));
System.out.println("元素索引: "+linkList.indexFind(4));
//删除指定的元素,返回索引
System.out.println("删除的元素位置:"+linkList.deletNode(5));
}
运行结果:
链表节点:
2 3 4 5
链表节点:
4 5
元素索引: 1
元素索引: 0
删除的元素位置: 1
单链表只维护一个首节点的位置,查找和删除都需要沿着首节点一个一个元素找下去。因为节点类只有一个对后一个元素的引用,所以在中间位置插入元素需要记录前一个元素的位置,删除同样也需要记录前一个节点的位置。
三、双端链表
双端链表和单链表实现类似,不同的是除了有对第一个链接点的引用,还有对最后一个链接的引用。可以在链表末尾方便的插入元素,至于遍历和查找、删除指定的元素和普通的单链表实现方法一致。
在链表末尾方便的删除的元素,双端链表也不可以,因为没有一个引用指向倒数第二个节点。假若需要方便的删除最后一个节点,需要实现双向链表。
public class FirstLastList {
ListNode first;
ListNode last;
public FirstLastList() {
this.first = null;
this.last = null;
}
//头插法
public void insertFirst(int data) {
ListNode newNode = new ListNode(data);
if (first == null) { //插入首个元素
last = newNode;
}
newNode.next = first;
first = newNode;
}
//尾插法
public void insertLast(int data) {
ListNode newNode = new ListNode(data);
if (this.isEmpty()) { //如果是插入首个元素
first = newNode;
last = newNode;
} else {
last.next = newNode;
last = newNode;
}
}
//删除首个元素
public void deleteFirst() {
if (!this.isEmpty()) { //判断是否非空
first = first.next;
if (first == null) { //判断是否非空
last = null;
}
}
}
//判断是否空
public boolean isEmpty() {
return first == null;
}
//遍历
public void display() {
//声明一个指针
ListNode cur = first;
System.out.println("链表节点:");
while (cur != null) {
System.out.print(cur.toString()+" ");
cur = cur.next;
}
System.out.println(" ");
}
//和单链表一样
//查找给定元素的索引
public int indexFind(int data) {
//声明一个指针
ListNode cur = first;
int count = 0;
while (cur != null) {
if (cur.data == data) {
return count;
} else {
cur = cur.next;
count++;
}
}
return -1;
}
}
测试代码:
@Test
public void testFirstLastList() {
FirstLastList firstLastList = new FirstLastList();
//测试头插法
firstLastList.insertFirst(100);
firstLastList.insertFirst(80);
firstLastList.insertFirst(50);
firstLastList.insertFirst(10);
firstLastList.display();
//测试尾插法
firstLastList.insertLast(1000);
firstLastList.insertLast(2000);
firstLastList.insertLast(6000);
firstLastList.display();
//测试查找
System.out.println("元素所在位置 "+firstLastList.indexFind(2000));;
}
运行结果:
链表节点:
10 50 80 100
链表节点:
10 50 80 100 1000 2000 6000
元素所在位置 5
双端链表适合实现队列。
三、有序链表
有序链表字面意思就是数据在链表中按关键字有序排列的。
有序链表相比有序数组,数据插入速度要快一些,而且链表的大小不固定,方便拓展。
有序链表中插入一个数据项,需要先找到插入位置,考虑插入位置在表头、表尾、中位置的情况。
public class SortedList {
ListNode first;
public SortedList() {
this.first = null;
}
//插入
public void insert(int data) {
ListNode newNode = new ListNode(data);
//声明一个指针,记录前一个元素
ListNode prev = null;
//声明一个指针
ListNode cur = first;
while (cur != null && cur.data <= data) {
prev = cur;
cur = cur.next;
}
if (prev == null) { //插入位置在表头或者链表为空
first = newNode;
} else { //其它位置,表中间位置或者表尾
prev.next = newNode;
}
newNode.next = cur; //都要进行的一步
}
//删除头元素
public ListNode deleteFirst() {
if (!isEmpty()) {
ListNode temp = first;
first = first.next;
return temp;
}
return null;
}
//判断非空
public boolean isEmpty() {
return first == null;
}
//遍历
public void display() {
//声明一个指针
ListNode cur = first;
System.out.println("链表节点:");
while (cur != null) {
System.out.print(cur.toString()+" ");
cur = cur.next;
}
System.out.println(" ");
}
//和单链表一样
//查找给定元素的索引
public int indexFind(int data) {
//声明一个指针
ListNode cur = first;
int count = 0;
while (cur != null) {
if (cur.data == data) {
return count;
} else {
cur = cur.next;
count++;
}
}
return -1;
}
}
测试代码:
@Test
public void testSortedList() {
SortedList sortedList = new SortedList();
//插入元素
sortedList.insert(100);
sortedList.insert(11);
sortedList.insert(23);
sortedList.insert(18);
sortedList.insert(1100);
sortedList.insert(1200);
sortedList.display();
//删除头元素
System.out.println("删除的最小元素是: "+sortedList.deleteFirst().toString());;
System.out.println("删除的最小元素是: "+sortedList.deleteFirst().toString());;
//查找
System.out.println("元素的索引是: "+sortedList.indexFind(100));
}
运行结果:
链表节点:
11 18 23 100 1100 1200
删除的最小元素是: 11
删除的最小元素是: 18
元素的索引是: 1
有序链表寻找、和删除指定元素同样需要沿着第一个链节点找,与单链表一样。不过删除最小的元素,只需要O(1)的时间,比较快速,即删除首元素方法。
五、双向链表
双向链表中每个链节点有两个指向其它链节点的引用,而不是一个,所以既允许向后遍历,也允许向前遍历。
双向链表每次插入或删除,要处理三个链节点的引用
双向链表不必是双端链表,实现了有助于删除最后一个元素
1.双向链表的节点类
class Node {
int data;
Node prev;
Node next;
public Node(int data) {
this.data = data;
this.prev = null;
this.next = null;
}
@Override
public String toString() {
return ""+data;
}
}
2.双向链表创建初始化,维护两个指针,方便访问首元素和尾元素
public class DoubleLinkList {
//采用双端链表的形式
Node first;
Node last;
public DoubleLinkList() {
this.first = null;
this.last = null;
}
//判断是否空
public boolean isEmpty() {
return first == null;
}
}
3.双向链表的插入方法相比单向链表,需要多维护队前一个链节点的引用.
//头插法
//与单链表相比需要维护链节点对前一个的引用
public void insertFirst(int data) {
Node newNode = new Node(data);
if (!isEmpty()) { //非空,不需要维护last指针
newNode.next = first;
first.prev = newNode;
first = newNode;
} else { //空,需要维护last指针
last = newNode;
first = newNode;
}
}
//尾插法,有双端链表的情况才有这种方法
//相比单链表,需要多维护链节点对前一个的引用
public void insertLast(int data) {
Node newNode= new Node(data);
if (!isEmpty()) { //非空,不需要维护first指针
last.next = newNode;
newNode.prev = last;
last = newNode;
}else { //空,需要维护first指针
first = newNode;
last = newNode;
}
}
测试代码:
@Test
public void testDoubleLinkList() {
DoubleLinkList doubleLinkList = new DoubleLinkList();
//测试头插法
doubleLinkList.insertFirst(19);
doubleLinkList.insertFirst(13);
doubleLinkList.insertFirst(100);
doubleLinkList.display();
//测试尾插法
doubleLinkList.insertLast(30);
doubleLinkList.insertLast(40);
doubleLinkList.insertLast(20);
doubleLinkList.display();
}
运行结果:
双向链表的元素:
100 13 19
双向链表的元素:
100 13 19 30 40 20
4.插入和删除首尾元素
双向链表同样可以删除首个元素和末尾元素,同样需要注意对前一个链节点引用的维护。对于只有一个元素,需要考虑维护首为指针。
//删除第一个链节点
//相比单链表,需要多链节点对前一个的引用
public void deleteFirst() {
if (!isEmpty()) { //判断非空
if (first.next != null) { //如果不止一个元素,不需要维护last指针
first.next.prev = null;
first = first.next;
} else { //如果只有一个元素,需要维护last指针
last = null;
first = null;
}
}
}
//删除最后一个节点,双向链表相比其它链表最方便的特性
public void deleteLast() {
if (!isEmpty()) { //判断非空
if (first.next != null) { //如果不止一个元素,不需要维护first指针
last.prev.next = null;
last = last.prev;
} else { //如果只有一个元素,需要维护last指针
first = null;
last = null;
}
}
}
测试代码:
//删除首个元素
doubleLinkList.display();
doubleLinkList.deleteFirst();
doubleLinkList.display();
//删除末尾元素
doubleLinkList.display();
doubleLinkList.deleteLast();
doubleLinkList.display();
运行结果 :
双向链表的元素:
100 13 19 30 40 20
双向链表的元素:
13 19 30 40 20
双向链表的元素:
13 19 30 40 20
双向链表的元素:
13 19 30 40
5.元素的插入和删除
双向链表的插入删除方法,需要维护三个链节点之间的应用。同样也需要插入删除位置在表头、表尾、表中间位置的情况。
//在某个特定的链节点插入元素
public boolean insertAfter(int key,int data) {
//声明一个指针
Node cur = first;
while (cur != null && cur.data != key) { //寻找要插入的元素位置
cur = cur.next;
}
if (cur == null) { //找不到退出
return false;
}
Node newNode = new Node(data); //找到
if(cur != last) { //要插入的位置不是末尾
//先于后一个链节点相连
newNode.next = cur.next;
cur.next.prev = newNode;
//再与前一个链节点相连
cur.next = newNode;
newNode.prev = cur;
} else { //插入的位置是末尾,要维护last指针
cur.next = newNode;
newNode.prev = cur;
last = newNode;
}
return true;
}
//删除元素
public boolean deleteKey(int key) {
if (isEmpty()) {
return false;
}
//声明一个指针
Node cur = first;
while (cur != null && cur.data != key) { //寻找位置
cur = cur.next;
}
if (cur == null) { //找不到退出
return false;
}
if (cur == first) { //删除的是第一个元素,需要维护first指针
first.next.prev = null;
first = first.next;
} else{ //删除其它位置元素
cur.prev.next = cur.next;
}
if (cur == last) { //删除的是末尾最后一个元素,需要维护last指针
last.prev.next = null;
last = last.prev;
} else {
cur.next.prev = cur.prev;
}
return true;
}
测试代码:
//测试在指定元素后面插入新元素
System.out.println("测试");
doubleLinkList.insertAfter(40,41);
doubleLinkList.display();
//测试删除指定的元素
doubleLinkList.deleteKey(13);
doubleLinkList.display();
//测试向后遍历
doubleLinkList.displayBackward();
运行结果:
测试
双向链表的元素:
13 19 30 40 41
双向链表的元素:
19 30 40 41
双向链表的元素:
41 40 30 19
6.遍历
双向链表可以向前遍历。
//向前遍历
public void displayBackward() {
Node cur = last;
System.out.println("双向链表的元素: ");
while (cur != null) {
System.out.print(cur.toString()+" ");
cur = cur.prev;
}
System.out.println(" ");
}