一.删除链表中所有值为x的节点
1.使用递归,无头结点
LinkList deleteAllX(LinkList L,int x) {
if (!L) return NULL;
if (L->data == x) {
LNode* p = L;
L = deleteAllX(L->next, x);
delete(p);
return L;
}
else {
LinkList result;
result = L;
L->next = deleteAllX(L->next, x);
return result;
}
}
2.有头节点
void deleteAllX(LinkList& L,int x) {
LNode* p1 = L;
LNode* p2 = L->next;
while (p2) {
if (p2->data == x) {
LNode* p = p2;
p1->next = p2->next;
p2 = p2->next;
delete(p);
}
else {
p1 = p2;
p2 = p2->next;
}
}
}
二.链表反向
方法一:头插法
在**O(1)**的空间复杂度内实现链表的反向输出
使用方法:将链表节点依次摘下使用头插法插入到头节点之后。
注意点:不需要创建新的头节点和头指针,直接在原链表上进行操作。
void reverse(LinkList &L) {
LNode* p1 = L->next;
L->next = NULL;
while (p1) {
LNode* p2 = p1->next;
p1->next = L->next;
L->next = p1;
p1 = p2;
}
}
方法二:使用指针r防止断链
void reverse(LinkList &L) {
//pre节点以及之前的节点的指针都已指向原前驱节点,p指向当前需要处理的节点,r用于保存后面的链表
LNode* pre = L->next;
LNode* p = pre->next;
LNode* r = p->next;
pre->next = NULL;
while (r) {
p->next = pre;
pre = p;
p = r;
r = r->next;
}
p->next = pre;
L->next = p;
}
三.删除链表中最小元素
void deleteMin(LinkList& L) {
if (!L->next) {
return;
}
LNode* p = L->next;
//当前最小值元素
int minVal = L->next->data;
//指向具有最小值的元素的前一个元素
LNode* minP = L;
while (p->next) {
LNode* p1 = p->next;
if (p1->data < minVal) {
minVal = p1->data;
minP = p;
}
p = p->next;
}
//删除操作
p = minP->next;
minP->next = p->next;
delete(p);
}
四.链表插入排序
注意点:
①当执行操作p->next=…时,要使用指针r将后续链表保存,防止断链。
②链表和顺序表插入排序不同处在于:先创建了一个只含一个元素的链表,将原链表中元素插入其中。
原因是:如果直接将链表结点插入原链表,当链表最尾端结点被插入前面,NULL将丢失。
//错误示范
LNode* p = L->next->next;
//循环插入
while (p) {
LNode* r = p->next;
LNode* pre = L;
while (pre->next->data <= p->data && pre->next != p) {
pre = pre->next;
}
p->next = pre->next;
pre->next = p;
p = r;
}
正确答案:
//使用直接插入排序法对链表进行排序
void sort(LinkList& L) {
//先构成一个只含一个元素的单链表
LNode* p = L->next->next;
L->next->next = NULL;
//循环插入
while (p) {
LNode* r = p->next;
LNode* pre = L;
while (pre->next->data <= p->data && pre->next != NULL) {
pre = pre->next;
}
p->next = pre->next;
pre->next = p;
p = r;
}
}
五.寻找两个链表的公共部分
LNode* pub(LinkList L1,LinkList L2) {
int count1 = length(L1);
int count2 = length(L2);
if (count1 > count2) {
int contrast = count1 - count2;
LNode* p1 = L1->next;
LNode* p2 = L2->next;
for (int i = 1; i <= contrast; ++i) {
p1 = p1->next;
}
while (p1 && p2) {
if (p1 == p2) {
return p1;
}
}
}
else {
int contrast = count2 - count1;
LNode* p1 = L1->next;
LNode* p2 = L2->next;
for (int i = 1; i <= contrast; ++i) {
p2 = p2->next;
}
while (p1 && p2) {
if (p1 == p2) {
return p1;
}
}
}
return NULL;
}
六.将链表按照奇偶数分割
1.原题
题目:将一个带有头节点的链表A分解成两个带头结点的链表A,B,其中A保存序号为奇数的结点,B保存序号为偶数的结点。保持两个链表中元素相对顺序不变。
我的解法:
void depart(LinkList& A, LinkList& B) {
//构造B的头结点
B = new LNode;
B->data = -1;
B->next = NULL;
LNode* p = A; //p用于保存A当前需要操作节点的前驱节点
LNode* tail = B; //tail指向B的尾部元素
LNode* r; //r用于防止A断链
for (int i = 1; p->next; ++i) {
if (i % 2 == 0) {//序号为偶数
r = p->next->next;
tail->next = p->next;
tail = tail->next;
p->next->next = NULL;
p->next = r;
}
else {
p = p->next;
}
}
}
注意:①p表示操作结点的前驱结点,方便将p->next插入B链表后,将p和原先的p->next->next连接上。
②使用r指针防止A断链
标准答案:先初始化两个链表A,B(A->next=NULL,B->next=NULL,然后将结点按奇偶性插入A或B表中,此方法无需对操作结点前一个结点后后一个结点进行连接)。
2.变式
题目:和1中一样,但是B中的元素要求和原来的顺序相反。
方法:对B使用头插法。
void depart(LinkList& A, LinkList& B) {
B = new LNode;
B->data = -1;
B->next = NULL;
LNode* p = A; //p用于保存A当前需要操作节点的前驱节点
LNode* r;
for (int i = 1; p->next; ++i) {
if (i % 2 == 0) { //操作结点序号为偶数
r = p->next->next;
p->next->next = B->next;
B->next = p->next;
p->next = r;
}
else {
p = p->next;
}
}
}
七.删除链表中重复元素
1.有序链表
void delDuplicate(LinkList& L) {
if (!L->next || !L->next->next) { //若链表为空或者只含一个有效结点,直接返回
return;
}
LNode* p = L->next;
LNode* q = L->next->next;
LNode* temp;
while (q) {
if (p->data == q->data) {
temp = q;
p->next = q->next;
q = p->next;
delete(temp);
}
else {
p = q;
q = q->next;
}
}
}
2.无序链表
高时间复杂度
void deleteDuplicate(LinkList L) {
LNode* p = L;
while (p->next) {
for (LNode* q = L->next; q != p->next && q; q = q->next) {
if (q->data == p->next->data) {
LNode* r = p->next;
p->next = p->next->next;
delete(r);
break;
}
}
p = p->next;
}
}
空间换时间法
八.链表合成
1.将两个升序链表L1和L2合成为一个降序链表
void merge(LinkList& L1, LinkList& L2) { //使用L1存放归并后的链表
LNode* p1 = L1->next;
LNode* p2 = L2->next;
LNode* r;
L1->next = NULL;
while (p1 || p2) {
if (!p2 || p1->data <= p2->data) {
r = p1->next;
p1->next = L1->next;
L1->next = p1;
p1 = r;
}
else if (!p1 || p1->data > p2->data) {
r = p2->next;
p2->next = L1->next;
L1->next = p2;
p2 = r;
}
}
delete(L2);//最后不要忘记释放L2的内存空间
}
2.将两个链表公共元素合成在一个链表中
例如:1,2,3,3,4,5和1,2,2,5
合成为1,1,2,2,2,5
void merge(LinkList& L1, LinkList& L2) { //使用L1存放归并后的链表
LNode* p1 = L1->next;
LNode* p2 = L2->next;
LNode* r;
int temp = -1;//保存刚刚插入的值,这个值在链表中不可能是-1
L1->next = NULL;
while (p1 || p2) {
if (p1 && p1->data == temp) {
r = p1->next;
p1->next = L1->next;
L1->next = p1;
p1 = r;
}
else if (p2 && p2->data == temp) {
r = p2->next;
p2->next = L1->next;
L1->next = p2;
p2 = r;
}
else if (p1 && p2) {
if (p1->data < p2->data) {
p1 = p1->next;
}
else if (p1->data > p2->data) {
p2 = p2->next;
}
else if (p1->data == p2->data) {
temp = p1->data;
}
}
}
}
九.链表子串判断
1.判断sub是不是L1的子串,其中sub和L1都是链表
暴力算法:
bool subStr(LinkList L1, LinkList sub) {
LNode* p1 = L1->next;
for (;p1;p1=p1->next) {
bool flag = true;
LNode* pI1 = p1;
LNode* pI2 = sub->next;
while (pI1 && pI2) {
if (pI1->data != pI2->data) {
flag = false;
break;
}
pI1 = pI1->next;
pI2 = pI2->next;
}
if (!flag) {
continue;
}
else {
return true;
}
}
return false;
}
十.判断双向循环链表是否对称
bool judge(LinkList L) {
LNode* p1 = L->next;
LNode* p2 = L->prior;
while (p1 != p2 && p1->next != p2) {
if (p1->data != p2->data) {
return false;
}
p1 = p1->next;
p2 = p2->prior;
}
return true;
}
十一.判断单链表是否存在环
使用方法:快慢指针法,如果存在环,快指针一定可以追上慢指指针。慢指针速度必须设置为1。
bool judge(LinkList L) {
LNode* fast = L->next;
LNode* slow = L->next;
while (fast && slow && fast->next) {
if (fast == slow) {
return true;
}
slow = slow->next;
fast = fast->next->next;
}
return false;
}
十二.找到倒数第k个元素(2009统考)
找到链表倒数第k个元素,输出其元素值,若不存在,返回0,存在返回1.
int find(LinkList L, int k) {
LNode* p = L->next;
int count = 0;
while (p) {
p = p->next;
++count;
}
p = L->next;
if (k > count) {
return false;
}
for (int i = 1; i <= count - k + 1; ++i) {
p = p->next;
}
cout << p->data;
return 1;
}
多遍扫描获得答案,得分:10分
正确答案:设置两个指针p和q,初始时候都指向链表第一个结点,p先移动,当移动了k个节点后,q开始移动。当p移动到链表末尾时,q则移动到倒数第k个结点。
十三.2019统考真题,链表部分倒置
void transform(LinkList L) {
LNode* p = L->next;
LNode* r;
int count;
//统计链表的长度count
for (count = 0; p; p = p->next) {
count++;
}
p = L->next;
LNode* p1 = L->next;
L->next = NULL;
//将链表后半部分内容以头插法插入L中
for (int i = 1; p; ++i) {
if (i == count / 2) {
r = p;
p = p->next;
r->next = NULL;
}
else if (i >= count / 2 + 1) {
r = p->next;
p->next = L->next;
L->next = p;
p = r;
}
else {
p = p->next;
}
}
p = L->next;
LNode* start = L;
//将链表前半段内容以头插法插入L中
while (p1) {
r = p1->next;
p1->next = start->next;
start->next = p1;
start = p1->next;
p1 = r;
}
}
十三.双向链表维持结点有序
原题:设头指针为L,有头结点的非循环双向链表,其每个节点有一个访问频度域freq,在链表被启用前,freq均为0.每当对链表进行一次Locate(L,x)运算时,对元素值为x的结点频度值增加1,并使链表中结点按访问频度非增顺序排列。同时最近访问的结点排在频度相同的结点前面,以使频繁访问的结点总是靠近表头。编写符合以上要求的Locate函数。返回找到的结点地址,类型为指针型。
LNode* Locate(LinkList& L, int x) {
LNode* p = L->next;
while (p) {
if (p->data == x) {
p->freq++;
LNode* q = p;
while (q->freq <= p->freq && q != L) { //将结点q插在结点p后面
q = q->prior;
}
if (q->next != p) { //若q的下一个就是p,则不需要对结点进行移动
//使用三个指针防止断链
LNode* r1 = p->next, * r2 = p->prior, * r3 = q->next;
p->next = r3;
p->prior = q;
q->next = p;
r3->prior = p;
r2->next = r1;
if (r1) {
r1->prior = r2;
}
}
break;
}
p = p->next;
}
return p;
}