使用链表结构可以克服数组链表需要预先知道数据大小的缺点
链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。
但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。
单向链表:
一个单链表的节点分为两部分:数据域(data)和指针域(next)
数据域用来保存信息,指针域用来指向下一个节点的地址
单向链表查询较慢: 查找时需要从第一个节点开始一直访问到需要的位置
插入快:
删除快:删除只需要将删除结点的上一个指针指向删除节点的下一个节点。
单向链表插入的三种方法:
1.头插法:
- 将新节点的
next
指针指向当前的头节点。 - 更新头节点指针为新节点。
public void insertAtHead(int data) {
Node newNode = new Node(data);
newNode.next = head;
head = newNode;
}
2.尾插法
- 如果链表为空(只有一个 head 指针并且值为null),直接将头节点指针指向新节点。
- 链表不为空,遍历到链表的最后一个节点,将其
next
指针指向新节点。
public void insertAtTail(int data) {
Node newNode = new Node(data);
if (head == null) {//只有一个 head 指针并且值为null
head = newNode;
return;
}
Node current = head;
while (current.next != null) {
current = current.next;
}
current.next = newNode;
}
3.插入到链表的指定位置
- 遍历到插入位置的前一个节点(位置从0开始计数)。
- 将新节点的
next
指针指向当前节点的next
。 - 更新当前节点的
next
指针为新节点。
public void insertAtPosition(int data, int position) {
if (position < 0) {
throw new IndexOutOfBoundsException("Position cannot be negative");
}
if (position == 0) {
insertAtHead(data);
return;
}
Node newNode = new Node(data);
Node current = head;
for (int i = 0; i < position - 1 && current != null; i++) {
current = current.next;
}
if (current == null) {
throw new IndexOutOfBoundsException("Position out of bounds");
}
newNode.next = current.next;
current.next = newNode;
}
双向链表:
双向链表的指针域有两个指针,分别指向直接后继和直接前驱。
双向链表可以从前向后遍历也可以从后向前遍历
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next; //后继节点
struct ListNode* prev; //前驱节点
LTDataType data;
}LTNode;
当双向链表为空时:
双向链表头插法:
对于每个元素,创建一个新结点,并将其插入到头结点之后,使新结点成为新的头结点。
最后创建成功的双链表的顺序和输入的顺序相反,即为逆序的。
头插法的时间复杂度为O(1)。
//利用头插法创建双链表
DLinkList Init_DLinkList_head() {
DLinkList head = (DLinkList) malloc(sizeof(DLinkList));
head->prior = NULL;
head->next = NULL;
head->data = NULL;
bool List_Insert (DLinkList pHead,int e){
DNode* newNode = (DNode *)malloc(sizeof(DNode));
newNode->data = e;
newNode->next = pHead->next; //head的后继赋给新节点的后继
pNode->prior = pHead; //头节点指向新节点
if(pHead->next !=NULL){
pHead->next->prior = newNode;
}
pHead->next= newNode;
return true;
}
}
双向链表尾插法:
对于每个元素,创建一个新结点,并将其插入到头结点之后的尾部,更新尾结点的next指针和新结点的prior指针。遍历完成后,返回头结点的next指针,即为新链表的头结点。
尾插法的时间复杂度为O(n),因为需要遍历整个双链表找到尾节点。
//利用尾插法创建双链表
DLinkList Init_DLinkList_tail() {
DLinkList head = (DLinkList) malloc(sizeof(DLinkList));
head->prior = NULL;
head->next = NULL;
head->data = NULL;
void addLast(DLinkList pHead,int e){
DNode* newNode = (DNode *)malloc(sizeof(DNode));
newNode->data = e;
}
DNode* last = pHead;
//遍历链表找到最后一个节点
while(last->next !=NULL){
last = last->next;
}
//让最后一个结点的后继指向新节点,新节点的后继指针置空
last->next = newNode;
newNode->prior = last;
newNode->next = NULL;
}