王道链表编程题目笔记

一.删除链表中所有值为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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值