前篇:
为了保证代码的准确性和做题的效率,后续的验证部分的一些代码对一些博主的代码进行复制。
(5)循环链表无头结点和头指针,删除已知结点的前驱,那么就要拿到已知结点的前驱的前驱才能进行操作。注意当删除的结点是L结点(即头结点)时要进行指针移动,否则L就指向空无法进行输出。
答案:
//删除前驱
LinkList DelPre(LinkList L, LNode* s) {
//p用来遍历锁定结点
LNode* p;
p = L;
while (true)
{
if (p->next->next==s)
{
//当已知结点的前驱为L结点时,要将L结点进行移位。否则删除后,L会指向空
if (p->next==L)
{
L = L->next;
}
p->next->next = NULL;
p->next = s;
break;
}
else
{
p = p->next;
}
if (p==L)
{
break;
}
}
return L;
}
验证:
//删除前驱
LinkList DelPre(LinkList L, LNode* s) {
//p用来遍历锁定结点
LNode* p;
p = L;
while (true)
{
if (p->next->next==s)
{
//当已知结点的前驱为L结点时,要将L结点进行移位。否则删除后,L会指向空
if (p->next==L)
{
L = L->next;
}
p->next->next = NULL;
p->next = s;
break;
}
else
{
p = p->next;
}
if (p==L)
{
break;
}
}
return L;
}
//创建没有头结点的循环链表
LinkList create_linklist()
{
int len; //用来存储节点的个数
int i; //for循环中的循环变量
int val; //用来存放节点的值域
LinkList Ll = NULL; //Ll用来存储指向第一个节点的指针
LinkList pre = NULL; //pre用来存储指向LNew指向节点的前驱节点的指针,初始指向第一个节点
//请求用户输入节点个数
printf("请输入节点的个数:\n");
scanf_s("%d", &len);
for (i = 0; i < len; i++)
{
//每循环一次生成一个新的节点
LinkList LNew = (LinkList)malloc(sizeof(LNode));
//判断是否分配成功
if (!LNew)
{
printf("动态内存分配失败,程序退出!\n");
exit(-1);
}
//请求用户输入新节点的值
printf("请输入第%d个节点的值\n", i + 1);
scanf_s("%d", &val);
//i = 0时,即循环第一次运行时(生成第一个节点时)
if (0 == i)
{
//将指向第一个节点的指针保存在Ll中,便于后续操作
Ll = LNew;
//初始化pre,使其指向第一个节点
pre = Ll;
}
//将用户输入的值存储到数据域
LNew->data = val;
//将原来的节点挂在新生成的节点上
pre->next = LNew;
//新节点变成当前的最后一个节点,需要将其指针域指向第一个节点,从而形成循环单链表
LNew->next = Ll;
//pre后移,使其始终指向LNew指向节点的前驱节点,便于进行将LNew指向节点的前驱节点(原来的节点)挂在LNew指向的节点(新生成的节点)上的操作
pre = LNew;
}
//提示用户链表创建成功
printf("链表创建成功!\n");
//返回指向第一个节点的指针
return Ll;
}
//不含头结点的循环单链表的遍历输出
void show_linklist(LinkList Ll)
{
LinkList p = Ll;
if (!Ll)
return;
while (true)
{
printf("%d-->", p->data);
p = p->next;
if (p == Ll)
break;
}
printf("\n");
return;
}
int main() {
LinkList L = create_linklist();
printf("初始链表");
show_linklist(L);
LNode* s = L;
s = s->next;
printf("删除已知结点前驱后的链表");
LinkList L2= DelPre(L, s);
show_linklist(L2);
}
执行结果:
(6)题目说了有序循环链表,且有头结点,且list1和list2是两个链表的表尾指针,最后合成一个带有头结点的有序循环链表。
答案只需要在合并的两个方法里挑一个就行。
为了验证代码的准确性,先创建两个有序循环列表,代码如下:
结构体定义
//定义结构体
typedef struct node {
int data;
struct node* next;
}LNode,*LinkList;
创建两个有序循环链表(注意malloc需要对应的头文件)
void createLinklist(LinkList list1,LinkList list2) {
//定义三个指针,node用来创建新结点,first用来定位表1头结点,second用来定位表2头结点
LNode* node,*first,*second;
//进行定位
first = list1;
second = list2;
for (int i = 1; i <10; i++)
{
node = (LinkList)malloc(sizeof(LNode));
if (i % 2 == 1) {
node->data = i;
list1->next = node;
list1 = node;
}else
{
node->data = i;
node->next = NULL;
list2->next = node;
list2 = node;
}
}
//让链表首尾相连
list1->next = first;
list2->next = second;
//定义两个变量进行循环链表的输出控制
int first1 = 0;
int second1 = 0;
//从第一个数据节点开始遍历链表
LinkList cur1 = first->next;
LinkList cur2 = second->next;
printf("合并前链表1\n");
while (first != cur1 )
{
printf("%d--->", cur1->data);
cur1 = cur1->next;
}
printf("\n合并前链表2\n");
while (second !=cur2)
{
printf("%d--->", cur2->data);
cur2 = cur2->next;
}
main函数
int main() {
//创建两个链表的头结点
LinkList list1 = (LinkList)malloc(sizeof(LNode));
LinkList list2 = (LinkList)malloc(sizeof(LNode));
list1->next = NULL;
list2->next = NULL;
createLinklist(list1, list2);
}
执行结果:
接下来进行合并,题目对合并没有特别要求,所以我们就按从小到大的顺序进行排列。有两种方法:1.利用malloc申请空间创建新结点(定为merge1函数)。 2.在原链表上进行修改(定为merge2函数)。
两个方法主要区别在于合并链表的头结点方式不同,其次合并的判定条件略有不同。
方法1:
图片:
代码:
//方法1:创建新结点的方法
void merge1(LinkList list1, LinkList list2) {
//p1,p2分别用来对两个链表进行遍历,h用来定位头结点
LNode *p1, *p2, *h;
//创建头结点
LinkList H = (LinkList)malloc(sizeof(LNode));
H->next = NULL;
//定位头结点
h = H;
//两个指针分别指向各自链表的第一个数据结点
p1 = list1->next->next;
p2 = list2->next->next;
//因为两个指针是从第一个数据结点进行遍历的,当遍历到各自链表的头结点时证明遍历完毕
while (p1!=list1->next&&p2!=list2->next)
{
//创建一个新的结点,插入合并的链表,这里主要原因是先前直接插入发现两个链表最后混在一起,为了去除影响使用该方法进行合并
LinkList cur = (LinkList)malloc(sizeof(LNode));
//谁小把谁合并进去
if (p1->data<=p2->data)
{
cur->data = p1->data;
p1 = p1->next;
}
else
{
cur->data = p2->data;
p2 = p2->next;
}
h->next = cur;
h = cur;
}
//合并肯定有一个链表先走完,没走完的链表继续进行合并
while (p1!=list1->next)
{
h->next = p1;
h = h->next;
p1 = p1->next;
}
while (p2 != list2->next)
{
h->next = p2;
h = h->next;
p2 = p2->next;
}
//头尾相连
h->next = H;
Test(H);
}
方法2:
//方法2:不创建新链表,直接在原来的链表上进行修改
void merge2(LinkList list1, LinkList list2) {
//H用来进行合并链表,P1,P2用来遍历两个链表,h用来定位合并链表的头结点
LNode *H, *p1, *p2,*h;
//因为是直接在原表上面进行修改,直接随便拿一个表的头结点,这里拿表1的头结点
H = list1->next;
//后面会把表1的头结点和后面断开,所以先让p1移动到表1的第一个数据节点上
p1 = H->next;
//将表1的头结点和后面的链表部分断开并让h定位
H->next = NULL;
list1->next = NULL;
h = H;
//让p2移动到表2的第一个数据结点
p2 = list2->next->next;
//注意和方法1的判定条件不一样,现在表1已经不是一个循环链表了,他的尾部的next是空的,且没有头结点;但是表2未受影响,所以判定条件一样
while (p1!=NULL&&p2!=list2->next)
{
LinkList cur = (LinkList)malloc(sizeof(LNode));
if (p1->data <= p2->data) {
cur->data = p1->data;
p1 = p1->next;
}
else
{
cur->data = p2->data;
p2 = p2->next;
}
h->next = cur;
h = cur;
}
while (p1 != NULL)
{
h->next = p1;
h = h->next;
p1 = p1->next;
}
while (p2 != list2->next)
{
h->next = p2;
h = h->next;
p2 = p2->next;
}
//现在H在尾部,h在头部,头尾相连
h->next = H;
//进行测试
Test(H);
}
测试合并是否正确:
//测试合并后的循环链表
void Test(LinkList head) {
//cur从头部开始进行遍历。
LNode* cur = head->next;
printf("\n合并后链表\n");
while (cur!=head)
{
printf("%d-->", cur->data);
cur = cur->next;
}
}
(7)这个题我拆成了两个方法来写,首先是将链表拆分成奇数链表和偶数链表,其次再进行排序。题目没有强调原来的链表有头结点,所以本次代码没有写头结点,关键是获得原来链表的结点的数值,所以有无头结点影响不大。
拆分:
//先将链表分为奇数链和偶数链
void divide(LinkList head) {
//p用来遍历已知链表,o和e进行链表的插入操作
LNode*o,*e, * p;
p = head;
//odd和even分别为链表头结点
LinkList odd = (LinkList)malloc(sizeof(LNode));
o = odd;
odd->next = NULL;
LinkList even = (LinkList)malloc(sizeof(LNode));
e = even;
e->next = NULL;
while (p)
{
//奇数链插入
if (p->data % 2 == 1) {
//进行尾插
o->next = p;
o = p;
//p先移动到后面继续进行遍历
p = p->next;
//将新链表和原来的断开,免得又出什么问题
o->next = NULL;
}
//偶数链插入
else
{
e->next = p;
e = p;
p = p->next;
e->next = NULL;
}
}
printf("奇数链为\n");
showlinklist(odd);
printf("偶数链为\n");
showlinklist(even);
LinkList L3=array(odd);
printf("排序后的奇数链");
showlinklist(L3);
LinkList L4= array(even);
printf("排序后的偶数链");
showlinklist(L4);
}
排序:思想是每次遍历找出一个最小值的结点并固定到当前已经确定顺序的链表最后面。进行多趟排序即可。
//对链表进行排序
LinkList array(LinkList L) {
//q用来每趟排序的链表的遍历,从l后开始进行遍历。
// 每趟排序后会确定当前链表剩余结点的最小值,l用来固定已经确定的结点的顺序
//min用来确定每趟排序中的最小结点。
//pre用来确定每趟排序的最小结点的前驱,这样才能将min提出来插入到l的后面。
LNode * q,*l,*min,*pre;
//初始先让l指向头结点
l = L;
//如果l.next不为空,说明没有到达尾部,那么还能继续进行排序
while (l->next != NULL)
{
//每趟排序给q,pre,min赋值。
q = l->next;
pre = l;
min = l->next;
//当q的next不为空时,还没到尾部,该趟排序还未结束
while (q->next) {
//min初始指向的刚好是第一个数据结点,q->next为第二个数据结点,
//而且为了让pre能更新,所以就需要用q->next
if ( q->next->data<min->data)
{
pre = q;
min = q->next;
}
//无论何种情况q都得进行遍历
q = q->next;
}
//将min从原本的位置拆除
pre->next = min->next;
//将min插入到l的后面,头插法。
min->next = l->next;
l->next = min;
//该趟排序已经结束,更新l
l = min;
}
return L;
}
验证:
//对链表进行排序
LinkList array(LinkList L) {
//q用来每趟排序的链表的遍历,从l后开始进行遍历。
// 每趟排序后会确定当前链表剩余结点的最小值,l用来固定已经确定的结点的顺序
//min用来确定每趟排序中的最小结点。
//pre用来确定每趟排序的最小结点的前驱,这样才能将min提出来插入到l的后面。
LNode * q,*l,*min,*pre;
//初始先让l指向头结点
l = L;
//如果l.next不为空,说明没有到达尾部,那么还能继续进行排序
while (l->next != NULL)
{
//每趟排序给q,pre,min赋值。
q = l->next;
pre = l;
min = l->next;
//当q的next不为空时,还没到尾部,该趟排序还未结束
while (q->next) {
//min初始指向的刚好是第一个数据结点,q->next为第二个数据结点,
//而且为了让pre能更新,所以就需要用q->next
if ( q->next->data<min->data)
{
pre = q;
min = q->next;
}
//无论何种情况q都得进行遍历
q = q->next;
}
//将min从原本的位置拆除
pre->next = min->next;
//将min插入到l的后面,头插法。
min->next = l->next;
l->next = min;
//该趟排序已经结束,更新l
l = min;
}
return L;
}
//先将链表分为奇数链和偶数链
void divide(LinkList head) {
//p用来遍历已知链表,o和e进行链表的插入操作
LNode*o,*e, * p;
p = head;
//odd和even分别为链表头结点
LinkList odd = (LinkList)malloc(sizeof(LNode));
o = odd;
odd->next = NULL;
LinkList even = (LinkList)malloc(sizeof(LNode));
e = even;
e->next = NULL;
while (p)
{
//奇数链插入
if (p->data % 2 == 1) {
//进行尾插
o->next = p;
o = p;
//p先移动到后面继续进行遍历
p = p->next;
//将新链表和原来的断开,免得又出什么问题
o->next = NULL;
}
//偶数链插入
else
{
e->next = p;
e = p;
p = p->next;
e->next = NULL;
}
}
printf("奇数链为\n");
showlinklist(odd);
printf("偶数链为\n");
showlinklist(even);
LinkList L3=array(odd);
printf("排序后的奇数链");
showlinklist(L3);
LinkList L4= array(even);
printf("排序后的偶数链");
showlinklist(L4);
}
int main() {
LinkList L = create_linklist();
printf("无头结点初始链表");
showlinklist(L);
divide(L);
}
执行结果: