头结点和头指针的区分:不管带不带头结点,头指针都始终指向链表的第一个结点;而头结点是带头结点的链表中的第一个结点,结点内通常不存储信息。
注意:以下代码均是C环境下,不支持C++中的引用传递&
typedef 在C、C++中对struct的影响
typedef表示类型定义的意思,typedef struct 是为了使用这个结构体方便,给结构体起个别名。
(1)在C中的区别是使用时,是否可以省去struct这个关键字:
若struct Node { }这样来定义结构体的话,在申请Node的变量时,需要这样写,struct Node node;
若typedef struct Node { } Node这样来定义结构体的话,在申请Node的变量时就可以这样写,Node node;
在C++中,typedef的这种用途二不是很大,但是理解了它,对掌握以前的旧代码还是有帮助的;
(1.1)在C中定义一个结构体类型要用typedef,更为方便
typedef struct LNode
{
int data;
} LNode; // 使用时先创建结构体指针LNode *L;
//
struct LNode
{
int data;
}*L ; // 方式一 就相当于 方式二的 struct Student *L
struct LNode *L;// 方式二
于是在声明变量的时候就可: LNode *L;LinkList L;,如果没有typedef就必须用struct LNode *L;来声明,
这里的LNode实际上就是struct LNode的别名。
(2)在C++中
在c++中如果用typedef的话,又会造成区别:
typedef struct LNode
{
int data;
} LNode; // LNode是一个结构体类型,使用时必须先创建LNode L 对象,L.data
//
struct LNode
{
int data;
} L; // LNode是一个对象,即struct LNode L,L.data
引用的一些规则如下:
(1)引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)。
(2)不能有NULL 引用,引用必须与合法的存储单元关联(指针则可以是NULL)。
(3)一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。
引用传递本质上也是地址传递,二者可以互相替代,都是为了解决参数传递中的COPY。
但是在函数中如果是引用,直接使用变量名就行了,而如果是指针那么就得加上*()。另外C里面不支持引用。
没错,本质上是相同的,都是传递地址,也可以相互替代。
但是,引用是C++一个相对比较新的特性,是为了克服指针的一些局限性。
比如指针的销毁比较不好控制,而用引用就不存在问题。
malloc函数:
(1)原型:void * malloc(int size);
表示malloc向系统申请分配size字节的内存空间,返回类型为void* 类型【不确定类型的指针】,所以返回之前必须经过类型强制转换,否则编译报错
(2)malloc只管分配内存,并不会初始化,其内存空间中的值可能是随机的。如果分配的这块空间原来没有被使用过,那么其中每个值都可能是0;否则,其中的每个值可能是空间里面遗留的各种各样的值。
(3)分配的空间不再使用时,要用free函数释放这块内存空间;
(4)int* p; p = (int *) malloc (sizeof(int));参数表示为这段内存空间分配的字节大小;分配1个int类型的空间
#include
#include // C99 中增加了bool类型
#include
typedef struct LNode // 定义单链表结点类型
{
int data; // 数据域
struct LNode *next; // 指针域
} LNode, *LinkList;
// typedef struct LNode LNode;
// typedef struct LNode *LinkList;
// LNode *L;
// LinkList L;
// LNode *L 、LinkList L 均表示声明一个指向单链表第一个结点的指针,但方式2代码可读性更高
typedef struct DNode //定义双链表结点类型
{
int data; // 数据域
struct DNode *prior, *next; // 前驱指针、后继指针
int freq; // 访问频度 某例题需要,临时加的
} DNode, *DLinkList;
// ----------单链表【带头结点】基本操作 start----------
// 建立单链表【头插法】
// 目的是通过表头指针的移动实现头插法
// T(n)=S(n)
// typedef struct LNode * 用地址传递而不是引用传递,c中不支持引用传递
LinkList List_HeadInsert(LinkList L)
{
L = (LinkList)malloc(sizeof(LNode)); // 创建头结点
L->next = NULL; // 初始为空链表
LNode *S; // 表头指针
int ch;
scanf("%d", &ch); // 输入结点值
while (ch != 9999)
{
S = (LNode *)malloc(sizeof(LNode)); // 创建新结点
S->data = ch;
S->next = L->next; // 因为首次 L->next=NULL ,于是首次插入的结点作为了尾结点
L->next = S; // 将新结点插入表中,L为头指针
scanf("%d", &ch);
}
printf("OK");
return L;
}
// 建立单链表【尾插法】
// 由于附设了一个指向表尾结点的指针,故时间复杂度同头插法相同
// 目的是通过表尾指针的移动实现尾插法
// T(n)=S(n)
LinkList List_TailInsert(LinkList L)
{
L = (LinkList)malloc(sizeof(LNode)); // 创建头结点
L->next = NULL; // 初始为空链表
LNode *S; // 表头指针
LNode *R = L; // 表尾指针 初始指向头结点
int ch;
scanf("%d", &ch);
while (ch != 9999)
{
S = (LNode *)malloc(sizeof(LNode)); // 创建新结点
S->data = ch;
R->next = S;
R = S; // R指向新的表尾结点
scanf("%d", &ch);
}
R->next = NULL; // 尾结点指针置空
return L;
}
// 查找【按位查找】
// 按位查找,重点是参数i>=0,因为第0位是头结点,第一位才是表的第一个结点
// T(n)=O(n)
LNode *GetElem(LinkList L, int i)
{
LNode *p = L->next;
if (i == 0)
return L;
if (i < 1)
return NULL;
int j = 1;
while (p && j < i)
{
p = p->next;
j++;
}
return p;
}
// 查找【按值查找】
// T(n)=O(n)
LNode *LocateElem(LinkList L, int elem)
{
LNode *p = L->next; // p指向表第一个结点
while (p != NULL && p->data != elem) // 从第一个结点开始查找
{
p = p->next;
}
return p; // 找到返回该结点指针,找不到返回NULL
}
// 插入结点
// 将值为x的新结点插入到单链表的第i个位置上,通常采用后插法
// 【头插法】先检查插入位置的合法性,然后找到第i-1个位置,再将新结点插入 T(n)=O(n)
// 【尾插法】先检查插入位置的合法性,然后找到第i个位置,再将新结点插入 T(n)=O(1)
void List_INsertNew(LinkList L, LNode *node, int i)
{
// LNode *p = GetElem(L, i - 1); // 查找待插元素的前驱结点
// node->next = p->next; // 插入是一定以待插元素结点为中心考虑
// p->next = node;
// LNode *temp;
// node->next = p->next;
// p->next = node;
// temp = p->data;
// p->data = node->data;
// node->data = temp;
}
// 删除结点【将单链表的第i(i>=1)个结点删除】
// 先判断i的合理范围,后查找表中第i-1个结点,即被删结点的前驱结点,再将第i个节点删除 T(n)=O(n)
void List_Delete(LinkList L, int i)
{
LNode *p = GetElem(L, i - 1); // 查找被删元素的前驱结点
LNode *q = p->next; // 令q指向被删元素
p->next = q->next; // 将*q结点从链中断开
free(q); // 释放被删元素结点的存储空间
q = NULL;
// 还有一种删除结点*p,T(n)=O(1)
// LNode *p = GetElem(L, i - 1); // 查找被删元素的前驱结点
// LNode *q = p->next; // 令q指向被删元素
// p->data = p->next->data; //和后继结点交换数据域
// p->next = q->next; // 将*q结点从链中断开
// free(q);
}
// 求长度
int List_Length(LinkList L)
{
int len = 0;
LNode *p = L->next;
while (p != NULL)
{
++len;
p = p->next;
}
return len;
}
// 打印链表
void List_Print(LinkList L)
{
LNode *p = L->next;
while (p != NULL)
{
// printf("ddddd");
printf("%d", p->data);
p = p->next;
}
printf("\n");
}
// ------------单链表【带头结点】基本操作 end----------
// ------------单链表练习 start----------
// 1、在不带头结点的单链表L中,删除所有值为x的结点,并释放其空间,假设值为x的结点不唯一,要求使用递归算法;
// 算法思路:test01(L, x)递归函数作用是删除以L为首结点的单链表中所有值为x的结点,
// test01(L->next, int x)便是删除以L->next为首届点的单链表中所有值为x的结点;
// 明白LinkList L作为参数真正的含义是重点,其次是free(q)释放代表的真是含义;
// free(void* ptr):
// 用来释放动态分配的内存空间,需要头文件;
// ptr为将要释放的内存空间的地址;
// free() 可以释放由 malloc()、calloc()、realloc() 分配的内存空间,以便其他程序再次使用;
// 只能释放动态分配的内存空间,并不能释放任意的内存,例如int a[10];free(a);写法是错误的,
// 如果ptr所指向的内存空间不是由上面的三个函数所分配的,或者已被释放,那么调用free()会有无法预知的情况发生;
// 如果ptr为NULL,那么free()不会有任何作用;
// free()不会改变ptr变量本身的值,调用free()后ptr仍然会指向相同的内存空间,但是此时该内存已无效,不能被使用,所以建议将ptr的值设置为NULL;
void test01(LinkList L, int x)
{
if (L == NULL) // 判空,递归出口
return;
LNode *p; // 指向待删除结点
if (L->data == x)
{
p = L; // p 指向待删结点
L = L->next; // L指向下一结点
free(p); // 释放p,即删除p所指向的结点
p = NULL; // free只是放了p指向的地址空间,但p自身不会被释放,p仍然指向被释放的地址空间,只不过当前该地址空间无效,避免p成为野指针,将其置空
test01(L, x);
}
else
{
test01(L->next, x);
}
}
// 2、在带头结点的单链表L中,删除所有值为x的结点,并释放其空间,假设值为x的结点不唯一
// 算法思路:p用来从头至尾扫描单链表,pre指向*p结点的前驱结点,
// 若找到,则删除,并使p继续扫描下一个结点;
// 若未找到,pre和p同步后移,
// 关键点在于:pre的实际作用是用来删除p结点,q的实际作用是释放p结点
void test02(LinkList L, int x)
{
LNode *pre = L; // L指向p的前驱
LNode *p = L->next; // P指向表第一个结点
LNode *q; // q指向待删结点
while (p != NULL)
{
if (p->data == x)
{
q = p; // q指向待删结点p
p == p->next; // 扫描器p扫描下一个结点
pre->next = p; // 删除*q结点
free(q); // 释放q指向的地址空间/释放*q结点
q = NULL;
}
else // 否则,pre和p同步后移
{
pre = p;
p = p->next;
}
}
}
// 3、设L为带头结点的单链表,编写算法实现从尾到头反向输出每个结点的值
// void test03(LinkList L)
// {
// if (L->next != NULL)
// test03_dg(L->next); // 第一个结点
// }
// void test03_dg(LinkList L)
// {
// if (L->next != NULL)
// test03_dg(L->next); // 第二个结点、最后一个结点
// if (L != NULL) // 输出前一个结点
// printf(L->data);
// // if (L->next == NULL)
// // return;
// // LNode *p = L->next; // p作为扫描器
// // LNode *q; // q作为实际操作
// // while (p != NULL)
// // {
// // q = p; // q指向p扫描到的结点
// // p = p->next; // p后移
// // }
// // 当p==NULL,q指向最后一个结点
// }
// 4、在带头结点的单链表L中删除一个最小值结点(假设最小值结点唯一)
// 算法思路:关键处在于如何删除q,常规的方法是使用前驱指针
// T(n)=O(n),S(n)=O(1)
void test04(LinkList L)
{
LNode *pre = L; // pre指向p的前驱
LNode *p = L->next; // p作为扫描器
LNode *q_pre = L; // q_pre指向q的前驱
LNode *q = L->next; // q指向最小值结点(待删结点)初始表第一个结点为最小值结点
while (p != NULL)
{
if (q->data > p->data)
{
q = p; // q指向新的最小值结点(待删结点) q->data = p->data;
q_pre = pre;
}
pre = p;
p = p->next;
}
q_pre->next = q->next; // 删除最小值结点
free(q);
q = NULL;
}
// 5、将带头结点的单链表L原地逆置,即S(n)=1
// 算法思路一,要求原地逆置,则只能在原单链表的基础上改动,
// 将头结点摘下,从第一个结点开始,依次插入到头结点的后面(头插法建立单链表),
// 直到最后一个结点为止,这样就实现了链表的逆置
void test05_01(LinkList L)
{
LNode *p = L->next; // p作为工作指针,初始指向第一个结点
LNode *suc;
// LNode *suc = p->next; // suc指向p的后继,初始指向第二个结点
// LNode *q; // q指向待插入结点
L->next = NULL; // 处理第一个结点,第一个结点作为最后一个结点,后继为NULL
while (p != NULL) // p==NULL,表明p为最后一个结点
{
// 从第二个结点开始
suc = p->next;
p->next = L->next; // p结点插入到头结点之后
L->next = p;
p = suc;
// L->next = suc;
// suc->next = p;
// suc = suc->next;
// p = p->next;
}
}
// 算法思路二,要求原地逆置,则只能在原单链表的基础上改动,
// 第一个结点作为最后一个结点,最后一个结点作为第一个结点,将当前指针指向前驱结点
// 由于需要向后操作也需要向前操作,因此声明前驱指针和后继指针实现
void test05_02(LinkList L)
{
LNode *pre = L; // pre指向p的前驱
LNode *p = L->next; // p为工作指针
LNode *suc = p->next; // suc指向p的后继
p->next = NULL; // 处理第一个结点,第一个结点此时要作为最后一个结点,因此后继为NULL
while (suc != NULL) // suc==NULL,表明p为最后一个结点
{
pre = p; // pre后移
p = suc; // p后移
suc = suc->next; // suc后移
p->next = pre; // 指针反转
}
L->next = p; // 处理最后一个结点,最后一个结点此时要作为第一个结点,因此前驱为头结点
}
// 6、将带头结点的单链表中所有元素递增有序
// 算法思路:采用直接插入排序的思想,先构成一个只含一个数据结点的有序单链表,
// 然后依次扫描单链表中剩下的结点*p(直至p==NULL为止),在有序表中通过比较查找插入*p的前驱结点*pre,
// 然后将*p插入到*pre之后
void test06(LinkList L)
{
LNode *p_pre;
LNode *p = L->next; // p为工作指针
LNode *p_suc = p->next; // suc指向p的后继,以保证不断链
p->next = NULL; // 构造只含有一个数据结点的有序表
p = p_suc; // p后移
while (p != NULL) // p==NULL,表明p为最后一个结点
{
p_suc = p->next; // p_suc后移 保存*p的后继结点指针
p_pre = L;
while (p_pre->next != NULL && p_pre->next->data < p->data)
{
p_pre = p_pre->next; // 在有序表中查找插入*p的前驱结点*pre
}
p->next = p_pre->next; // 将*p插入到*pre之后 属于头插还是尾插法
p_pre->next = p;
p = p_suc; // 扫描原单链表中剩下的结点
}
}
// 7、带头结点的单链表无序,删除表中所有介于给定的两个值(作为函数参数给出)之间的元素(若存在)
// 算法思路:遍历,寻找,删除
void test07(LinkList L, int min, int max)
{
LNode *p_pre = L; // p_pre指向p的前驱
LNode *p = L->next; // p作为扫描器
while (p != NULL)
{
if (p->data > min && p->data < max) // (1,4) 12345 145
{
p_pre->next = p->next;
free(p);
p = NULL;
p = p_pre->next; // p后移
// p = p_suc; // p后移
// p_pre = p_pre->next; // p_pre后移
// p_suc = p_suc->next; // p_suc后移
}
else
{
p_pre = p; // p_pre后移
p = p->next; // p后移
}
}
}
// 8、给定两个单链表,找出两个链表的公共结点
// 算法思路:首先明白两个单链表有公共结点的实质是什么,由于单链表中每个结点的next指针域是唯一的,
// 所以当存在公共结点时,其后面的所有结点一定是相同的,拓扑形状看起来像Y,而不是X,
// 而且Y一定是口朝左的形式,因为x和口朝右的Y形式均不符合单链表中每个结点的next指针域是唯一的这一特点,
// 根据这个结论可知,如果两个链表存在公共节点的话,它们的尾结点也肯定是相同的,
// 方法一:暴力求解,遍历链表L1每一个结点,同时循环嵌套遍历链表L2的所有结点,作比较,直至找到相同的结点,T(n)=O(len1*len2)
// 方法二:优化求解,第一个相同的结点就是第一个公共的结点,此后直到尾结点都属于公共结点,T(n)=O(len1+len2)
// 测试时,L1,L2的设计需符合Y型
LinkList test08(LinkList L1, LinkList L2)
{
int len1 = List_Length(L1); // L1链表长
int len2 = List_Length(L2); // L2链表长
LinkList longList;
LinkList shortList;
int dist = 0;
if (len1 > len2)
{
longList = L1->next;
shortList = L2->next;
dist = len1 - len2;
}
else
{
longList = L2->next;
shortList = L1->next;
dist = len2 - len1;
}
while (dist--) // 长指针后移dist,保证两个指针同步
{
longList = longList->next;
}
while (longList != NULL)
{
if (longList == shortList)
{
return longList;
}
else
{
longList = longList->next;
shortList = shortList->next;
}
}
return NULL;
}
// 9、给定一个带头结点的单链表,按递增次序输出单链表中各结点的数据元素,并释放结点所占的存储空间
// 要求(不允许使用数组作为辅助空间)
// 算法思路:遍历链表,找到最小元素值,输出并释放,循环遍历,直至链表为NULL
void test09(LinkList L)
{
while (L->next != NULL) // 循环到仅剩头结点
{
LNode *p_pre = L; // p_pre作为p的前驱指针
LNode *p = L->next; // p作为工作指针
LNode *min;
while (p->next != NULL)
{
if (p->next->data < p_pre->next->data)
p_pre = p; // 记住当前最小值结点的前驱
p = p->next;
}
printf("%d", p_pre->next->data); // 输出元素最小值结点的数据
min = p_pre->next; // 删除最小值结点,并释放结点空间
p_pre->next = min->next;
free(min);
min = NULL;
}
free(L);
}
// 10、将一个带头结点的单链表A分解为两个带头结点的单链表A和B,使得A表中含有原表中序号为奇数的元素,
// 而B表中含有原表中序号为偶数的元素,且保持其相对顺序不变
// 算法思路:设置一个访问序号变量,初始为0,访问自增,根据奇偶性插入A表和B表中,重复操作直至表尾,
// 插入采用尾插法,以保证原结点顺序
void test10(LinkList A, LinkList B)
{
int num = 0; // 序号标记
LinkList B = (LinkList)malloc(sizeof(LNode)); // 创建B表表头
B->next = NULL; // 将B表置空
LNode *a_suc = A; // a_suc作为A表的尾指针
LNode *b_suc = B; // b_suc作为B表的尾指针
LNode *p = A->next; // p作为链表工作指针
A->next = NULL; // 将A表置空
while (p != NULL)
{
num++;
if (num % 2 == 0)
{
b_suc->next = p;
b_suc = p; // b_suc后移
}
else
{
a_suc->next = p;
a_suc = p; // a_suc后移
}
p = p->next; // p后移
}
a_suc->next = NULL;
b_suc->next = NULL;
}
// 11、设C={a1,b1,a2,b2,...,an,bn}为线性表,采用带头结点的hc单链表存放,原地变化将其拆分为两个线性表,
// 使得A={a1,a2,..,an},B={bn,...,b2,b1}
// 算法思路:同上题,只是不设序号变量,区别在于此处对B表的建立采用了头插法,
// 关键点在于,采用头插法插入结点后,*p的指针域以改变,若不设置变量保存其后继结点,则会引起断链,导致算法出错
void test11(LinkList A, LinkList B)
{
LinkList B = (LinkList)malloc(sizeof(LNode)); // 创建B表表头
B->next = NULL; // 将B表置空
LNode *p = A->next; // p作为链表工作指针
LNode *a_suc = A; // a_suc作为A表的尾指针
LNode *q;
A->next = NULL; // 将A表置空
while (p != NULL)
{
a_suc->next = p; // 将*p链到A表【尾插】
a_suc = p;
p = p->next;
if (p != NULL)
{
q = p->next; // 头插后,*p会断链,因此用q来记忆*p的后继
p->next = B->next; // 将*p链到B表,此时*p会断链【头插法】
B->next = p;
p = q; // p后移
}
}
a_suc->next = NULL;
}
// 12、在一个递增有序的单链表中,去掉数值相同的元素,使表中不再有重复的元素,例如(7,10,10,21)变为(7,10,21)
// 算法思路:注意是有序的单链表,所以相同值域的结点的存储地址是相邻的,
// 于是用p扫描递增单链表,若*p结点的值域等于其后继结点的值域,则删除后者,否则p移向下一个结点
void test12(LinkList L)
{
LNode *p = L->next; // p作为链表工作指针
LNode *q; // q指向*q的后继结点
if (p == NULL)
return;
while (p->next != NULL)
{
q = p->next;
if (p->data == q->data)
{
p->next = q->next; // 删除q结点并释放其内存空间
free(q);
}
else
{
p = p->next; // p后移
}
}
}
// 13、假设有两个递增次序排列的单链表形式,将两个单链表归并为一个按元素值递减次序排列的单链表,
// 并要求利用原来两个单链表的结点存放归并后的单链表
// 算法思路:注意是两个递增有序的单链表,将其合并时,均从第一个结点进行比较,将较小的结点插入链表,
// 同时后移工作指针,由于要求结果链表是递减有序的,故新链表的建立应该采用头插法,
// 比较结束后,可能会有一个链表非空,此时接着采用头插法将剩下的结点依次插入新链表即可
void test13(LinkList A, LinkList B)
{
LNode *pa = A->next; // pa是表A的工作指针
LNode *pb = B->next; // pb是表B的工作指针
LNode *q; // q暂存当前指向结点的后继结点
A->next = NULL; // 将A作为结果链表的头指针,置空表
while (pa != NULL && pb != NULL)
{
if (pa->data <= pb->data)
{
q = pa->next; // q暂存pa的后继结点指针
pa->next = A->next;
A->next = pa; // pa插入A【头插法】
pa = q; // 恢复pa为当前待比较结点
}
else
{
q = pb->next; // q暂存pb的后继结点指针
pb->next = A->next;
A->next = pb;
pb = q;
}
}
if (pa != NULL)
pb = pa;
while (pb != NULL)
{
q = pb->next;
pb->next = A->next;
A->next = pb;
pb = q;
}
free(B); // 释放B的头结点
}
// 14、带头结点的两个递增有序单链表A、B,将其中的公共元素存储到新链表C中,要求不破坏A、B的结点
// 注意:公共元素不同于公共结点,后者还包括指针域,而前者仅指数据元素,
// 算法思路:若A、B均有序,从A、B的第一个元素开始比较,
// 若元素值不等,则将值小的指针后移,
// 若元素值相等,则创建一个值等于公共结点的元素值的新结点,使用尾插法插入到新表中,
// 并将两个原表指针后移,直至其中一个链表遍历到表尾
LinkList test14(LinkList A, LinkList B)
{
LinkList C = (LinkList)malloc(sizeof(LNode)); // 建立新链表C
LNode *pa = A->next; // pa作为表A的工作指针
LNode *pb = B->next; // pb作为表B的工作指针
LNode *c_suc = C; // c_suc始终指向表C的尾结点
LNode *s; // s指向动态分配的新结点
while (pa != NULL && pb != NULL)
{
if (pa->data < pb->data)
{
pa = pa->next;
}
else if (pa->data > pb->data)
{
pb = pb->next;
}
else
{
s = (LNode *)malloc(sizeof(LNode));
s->data = pa->data; // 复制产生结点*s
c_suc->next = s; // 将*s链接到C上【尾插法】
c_suc = s; // c_suc重置
pa = pa->next;
pb = pb->next;
}
}
c_suc->next = NULL; // 置C尾结点指针为空
}
// 15、带头结点的两个递增有序单链表A、B,将其交集存储到链表A中
// 注意:交集值得是公共元素,并不是公共结点,
// 删除指既要删除也要释放,而释放仅仅指释放,
// 删除指将不需要的结点断开,然后将其前驱和后继结点建立链接,之后再释放已删除结点,
// 算法思路:采用归并思想,对两个链表进行扫描,只有同时出现在两链表中的元素才链接到结果表中且仅保留一个,
// 其他的结点全部释放,当一个链表遍历完后,释放另一个链表中剩下的全部结点
LinkList test15(LinkList A, LinkList B)
{
LNode *pa = A->next; // pa作为表A的工作指针
LNode *pb = B->next; // pb作为表B的工作指针
LNode *q_pre = A; // 结果表中当前合并结点的前驱指针
LNode *temp; // 用于记忆待释放结点
// A->next = NULL;
while (pa != NULL && pb != NULL)
{
if (pa->data == pb->data)
{
q_pre->next = pa; // A中结点链接到结果表中
q_pre = pa;
pa = pa->next;
temp = pb; // B中结点释放
pb = pb->next;
free(temp);
temp = NULL;
}
else if (pa->data < pb->data)
{
temp = pa;
pa = pa->next;
free(temp);
temp = NULL;
}
else
{
temp = pb;
pb = pb->next;
free(temp);
temp = NULL;
}
}
while (pa != NULL)
{
temp = pa;
pa = pa->next;
free(temp);
temp = NULL;
}
while (pb != NULL)
{
temp = pb;
pb = pb->next;
free(temp);
temp = NULL;
}
q_pre->next = NULL;
free(B);
B = NULL;
return A;
}
// 16、两个整数序列A=a1,a2,...,am,B=b1,b2,...,bn,已经存入单链表A、B,判断B是否为A的连续子序列
bool test(LinkList A, LinkList B)
{
LNode *pa = A->next; // pa作为表A的工作指针
LNode *pb = B->next; // pb作为表B的工作指针
LNode *q = pa; // q记忆每趟比较中A链表的开始结点
while (pa != NULL && pb != NULL)
{
if (pa->data == pb->data)
{
pa = pa->next;
pb = pb->next;
}
else
{
q = q->next; // q后移
pa = q; // A链表从新结点开始比较
pb = B->next; // B链表从第一个结点开始比较
}
}
if (pb == NULL) // B已经比较结束
return true; // B是A的子序列
else
return false; // B不是A的子序列
}
// 22、一个带头结点的单链表,查找链表中倒数第k个位置的结点(k为正整数),并输出该结点值
int test22(LinkList L, int k)
{
LNode *p = L->next;
LNode *q = L->next;
int count = 0;
while (p != NULL) // 遍历直至最后一个结点
{
if (count < k)
count++;
else
q = q->next;
p = p->next;
}
if (count < k)
return 0;
else
{
printf("%d", q->data);
return 1;
}
}
// 23、两个带头结点的单链表A、B,寻找A、B公共结点的起始位置并返回
int test23(LinkList A, LinkList B)
{
int len1 = List_Length(A); // L1链表长
int len2 = List_Length(B); // L2链表长
LNode *p;
LNode *q;
for (p = A; len1 > len2; len1--) // 若len1>len2,使p指向第(len1-len2)+1个结点
p = p->next;
for (q = B; len1 < len2; len2--) // 若len1
q = q->next;
while (p->next != NULL && p->next != q->next) // 将指针p和q同步向后移动
{
p = p->next;
q = q->next;
}
return p->next; // 返回公共后缀的起始地址
}
// 24、一个单链表,存储m个整数,元素值的绝对值小于等于n,
// 现对表中绝对值相等的结点,仅保留第一次出现的结点而删除其余的结点
// 要求,时间复杂度尽可能高
// T(n)=O(m),S(n)=O(n)
// 算法思想:核心思想是空间换时间,使用辅助数组记录链表中的已出现的数值,从而只需要对链表进行一趟扫描
// 因为|data|<=n,故arr的长度初始化为n+1,并初始化为0
// 依次扫描链表中各结点,同时检查arr[|data|]的值,若是0则保留该结点,并令arr[|data|]=1,否则将该结点从链表中删除
void test24(LinkList L, int n)
{
LNode *p = L->next; // p作为工作指针,指向第一个结点
LNode *q; // 指向待删除结点
int temp; // 存储临时数据
int *arr = (int *)malloc(sizeof(int) * (n + 1)); // 申请n+1个位置的辅助空间
for (int i = 0; i < n + 1; i++) // 初始化数组元素为0
*(arr + i) = 0;
while (p != NULL)
{
temp = p->data > 0 ? p->data : -p->data;
if (*(arr + temp) == 0) // 判断该结点的元素值是否首次出现 arr[|data|]
{
*(arr + temp) = 1; // 首次出现
p = p->next; // 保留
}
else // 重复出现
{
q = p; // q指向待删结点p
p = q->next; // p后移
free(q); // 释放待删除结点
q = NULL;
}
}
free(arr); // 释放数组
arr = NULL;
}
// 25、一个带头结点的单链表L=(a1,a2,a3,...,an-2,an-1,an),
// 现重新排列,使得L=(a1,an,a2,an-1,a3,an-2,...),
// 要求S(n)=O(1)
void test25(LinkList L)
{
LNode *p = L->next;
LNode *q = L->next;
LNode *temp;
LNode *s;
while (q != NULL) // 寻找链表中间结点
{
p = p->next; // p走一步
q = q->next;
if (q->next != NULL)
q = q->next; // q走两步
}
q = p->next; // p指向中间结点,q指向后半段的首结点
p->next = NULL;
while (q != NULL) // 将后半段逆置
{
temp = q->next;
q->next = p->next;
p->next = q;
q = temp;
}
s = L->next; // s指向前半段的第一个数据结点,即插入点
q = p->next; // q指向后半段的第一个数据结点
p->next = NULL;
while (q != NULL) // 将后半段插入到指定位置
{
temp = q->next; // temp指向后半段的下一个结点
q->next = s->next; // 将q所指结点插入到s所指结点之后
s->next = q;
s = q->next; // s指向前半段的下一个插入点
q = temp;
}
}
// ------------单链表练习 end----------
// ------------带环单链表练习 start ----------
// 21、判断一个单链表是否存在环,
// 单链表有环,是指单链表的最后一个结点的指针指向了链表中的某个结点(通常单链表的最后一个结点的指针域是空的)
void test21(LinkList L)
{
LNode *fast = L->next; // 快指针
LNode *slow = L->next; // 慢指针
while (fast != NULL && fast->next != NULL)
{
slow = slow->next; // 走一步
fast = fast->next->next; // 走两步
if (slow == fast)
break; // 相遇
}
if (slow == NULL && fast->next == NULL)
return false; // 无环
LNode *p = L->next; // p指向开始点
LNode *q = slow; // q指向相遇点
while (p != q)
{
p = q->next;
q = q->next;
}
return p; // 返回入口点
}
// ------------带环单链表练习 end----------
// ------------双链表练习 start----------
// 20、设头指针为L的带头结点的非循环双向链表,其每个结点中除有pre、data、next域外,还有一个访问频度域freq,
// 在链表被启用前,其值均初始化为零,
// 现要求每当在链表中进行一个Locate(L,x)运算时,令元素值为x的结点中freq域的值增1,
// 并使此链表中结点保持按访问频度递减的顺序排列,同时最近访问的结点排在频度相同的结点前面,
// 以便使频繁访问的结点总是靠近表头,最后返回找到结点的地址,类型为指针型
DLinkList test20(DLinkList DL, int x)
{
DNode *p = DL->next;
DNode *q;
while (p && p->data != x) // 查找值为x的结点
p = p->next;
if (!p)
exit(0);
else
{
p->freq++; // 令元素值为x的结点中freq域的值增1
if (p->freq == DL || p->prior->freq)
return p;
if (p->next != NULL)
p->next->prior = p->prior;
p->prior->next = p->next;
q = p->prior;
while (q != DL && q->freq <= p->freq)
q = q->prior;
p->next = q->next;
if (q->next != NULL)
q->next->prior = p;
p->prior = q;
q->next = p;
}
return p;
}
// ------------双链表练习 end----------
// ------------循环链表练习 start----------
// 17、判断带头结点的循环双链表是否对称
// 算法思路:
int test17(DLinkList DL)
{
DNode *p = DL->next;
DNode *q = DL->prior;
while (p != q && p->next != q)
{
if (p->data == q->data)
{
p = p->next;
q = q->next;
}
else
{
return 0;
}
}
return 1;
}
// 18、两个循环单链表A、B,将B链接A之后,要求链接后的链表仍保持循环链表形式
void test18(LinkList A, LinkList B)
{
LNode *p, *q;
p = A;
while (p->next != A)
{
p = p->next;
}
q = B;
while (q->next != B)
{
q = q->next;
}
p->next = B;
q->next = A;
return A;
}
// 19、带头结点的循环单链表,其结点值均为正整数,反复找出表中结点值最小的结点并输出,
// 然后将该结点从中删除,直到单链表为空为止,再删除头结点
void test19(LinkList L)
{
LNode *p, *pre, *minp, *minpre;
while (L->next != NULL)
{
p = L->next;
pre = L;
minp = p;
minpre = pre;
while (p != NULL)
{
if (p->data < minp->data)
{
minp = p;
minpre = pre;
}
pre = p;
p = p->next;
}
printf("%d", minp->data);
minpre->next = minp->next;
free(minp);
}
free(L);
}
// ------------循环链表练习 end----------
int main()
{
LNode node;
LinkList L = List_TailInsert(&node);
List_Print(L);
test09(L);
// List_Print(L);
// List_Print(L);
// int len = List_Length(L);
// printf("%d", len);
// test07(L, 1, 4);
// List_Print(L);
return 0;
}