链表的优缺点
优点:
-
可以充分利用碎片化的存储空间,不用像数组那样必须占用一片连续的空间,方便随时插入,只需改变前后结点(如果是双向链表)的指针域即可,不用移动大量数据。
缺点:
-
查询第一个元素和最后一个元素的时间不同,必须从头结点进入,遍历其前所有节点,若链表很长,则查询速度将远低于数组类型。
基本概念
结点:数据元素的存储映像。由 数据域+指针域构成。
单链表:数据域+1个指针域
双链表:数据域+2个指针域(一个指向前继结点,一个指向后继结点)
循环链表:首尾相接的链表
头指针:通常使用“头指针”来标识一个链表,如单链表L,头指针为NULL的时表示一个空链表。
头结点:在单链表的第一个结点之前附加一个结点,称为头结点。头结点的Data域可以不设任何信息,也可以记录表长等相关信息。但头结点本身不在链表长度统计范围之内。
[注意]无论是否有头结点,头指针始终指向链表的第一个结点。如果有头结点,头指针就指向头结点。
头结点的好处:
单链表的定义
//定义顺序链表
struct ListNode
{
int val;
ListNode *next;
};
创建顺序链表(头插法--有头结点)
void creatList_H_is_H(ListNode *&head, int length)
{
head = new ListNode;
head->next = NULL;
for (int i = 0; i < length; i++)
{
ListNode *p = new ListNode;
cout << "请输入val: ";
cin >> p->val;
p->next = head->next;
head->next = p;
}
}
创建顺序链表(头插法--无头结点)
void creatList_H_not_H(ListNode *&head, int length)
{
head = new ListNode;
head->next = NULL;
for (int i = 0; i < length; i++)
{
ListNode *p = new ListNode;
cout << "请输入val: ";
cin >> p->val;
p->next = head->next;
head->next = p;
}
head = head->next;//只用在返回时返回其头结点的后继节点的地址即可
}
创建顺序链表(尾插法)
void creatList_R(ListNode *&head, int length)
{
head = new ListNode;
head->next = NULL;
ListNode *r = head;
for (int i = 0; i < length; i++)
{
ListNode *p = new ListNode;
cout << "请输入val: ";
cin >> p->val;
p->next = NULL;
r->next = p;
r = p;
}
}
显示顺序链表(有头结点)
void displayList_is_H(ListNode *head)
{
cout << "遍历该链表为:" << endl;
ListNode *p = head->next;//只需使第一个输出的结点由头结点指向首元结点即可
;
while (p != NULL)
{
cout << p->val << " ";
p = p->next;
}
cout << endl;
}
显示顺序链表(无头结点)
void displayList_not_H(ListNode *head)
{
cout << "遍历该链表为:" << endl;
ListNode *p = head;//直接输出即可,不用后移
while (p != NULL)
{
cout << p->val << " ";
p = p->next;
}
cout << endl;
}
求链表长度(有头结点)
int get_ListLength_is_H(ListNode *head)
{
int i = 0;
ListNode *p = head->next;
while (p != NULL)
{
i++;
p = p->next;
}
return i;
}
求链表长度(无头结点)
int get_ListLength_not_H(ListNode *head)
{
int i = 0;
ListNode *p = head;
while (p != NULL)
{
i++;
p = p->next;
}
return i;
}
删除倒数第n个结点
ListNode *removeNthFromEnd(ListNode *head, int n)
{
ListNode *dummy = new ListNode; //定义头指针,这样头结点也有前驱节点,这样也可以删除头结点
dummy->next = head; //头指针连接头结点
int length = get_ListLength_is_H(head);
int i = 0;
ListNode *p = dummy;
while (i < length - n + 1)//将p定位到删除结点的前一个结点
//如果是正数删除,则括号判断内容需要改变
{
p = p->next;
++i;
}
ListNode *q = p->next;
p->next = q->next;
ListNode *ans = dummy->next;
delete q;
delete dummy;
return ans;
}
反转链表(无头结点)
ListNode *reverseList_not_H(ListNode *head)
{
ListNode *L = new ListNode; //新链表的头结点
L->next = NULL;
//采用头插法
while (head != NULL)
{
ListNode *q = new ListNode;
q->val = head->val;
q->next = L->next;
L->next = q;
head = head->next;
}
return L->next;
}
合并两个有序链表
ListNode *mergeTwoLists_not_H(ListNode *list1, ListNode *list2)
{
// ListNode *p1 = list1; //指向list1第一个元素
// ListNode *p2 = list2; //指向list2第一个元素
ListNode *p3 = new ListNode;
p3->next = NULL; //初始化p3结点
ListNode *p = p3;
while (list1 != NULL && list2 != NULL)
{
if (list1->val <= list2->val)
{
p->next = list1;
list2 = list1->next;
}
else
{
p->next = list2;
list1 = list2->next;
}
p = p->next;
}
p->next = (list1 == NULL) ? list2 : list1;
return p3->next;
}
环形链表
//利用数组查找迅速的特性查找环形链表
bool hasCycle(ListNode *head)
{
if (head == NULL)
return false; //防止是空链表,提高代码健壮性
vector<ListNode *> tmp; //构建动态数组
tmp.push_back(head);
vector<ListNode *>::iterator it;
while (head->next != NULL)
{
it = find(tmp.begin(), tmp.end(), head->next);
if (it != tmp.end()) //如果在数组当中找到,则实现环链表
{
return true;
}
else
{
head = head->next;
}
tmp.push_back(head);
}
//如果执行到这一步,说明到达链尾并且没有回环
return false;
}