前言
前面已经讲了数据在逻辑上有集合、线性、树形、图形这些结构,并且也已经学习了线性结构是什么?
这篇文章就是来做一些跟线性表相关的练习题。
下面是这些练习题都需要使用到的相关定义及函数
// 定义节点
typedef struct _Node {
int data;
struct _Node *next;
} Node;
// 链表
typedef Node* LinkList;
// 初始化链表
LinkList initLinkList(void) {
LinkList ll = malloc(sizeof(Node)); // 头节点
ll->data = 0;
ll->next = NULL;
// 循环添加节点
int data = 0;
Node *p = ll; // p指向最后一个节点
printf("请输入一个递增链表:(非递增时自动退出输入)\n");
scanf("%d", &data);
while (data >= p->data) {
// 创建节点
Node *node = malloc(sizeof(Node));
node->data = data;
node->next = NULL;
// 链接到链表
p->next = node;
p = node;
// 输入下一个数
scanf("%d", &data);
}
return ll;
}
void foreachLinkList(LinkList ll) {
Node *p = ll->next; // ll指向头节点,ll->next指向真正的第一个节点
while (p) {
printf("%-5d", p->data);
p = p->next;
}
printf("\n");
}
练习题
题目一
将2个递增
的有序
链表合并为一个链表的有序
链表,要求:
- 结果链表仍然使⽤用两个链表的存储空间,不另外占⽤用其他的存储空间。
- 表中不允许有重复的数据。
题目分析:
不能使用其他存储空间,意味着不能开辟除了这两个链表以外的其他堆空间,因此我们可以将链表2中的节点给按大小顺序插入到链表1中。
多余节点仍然可以先保留在链表2中,需要释放时可以直接遍历链表2,释放全部节点。
举例:
链表1: 1 3 5 7 8 10 12
链表2: 2 4 6 8 10 12
// 合并后的结果是
链表3: 1 2 3 4 5 6 7 8 10 12
代码:
// 合并递增链表
LinkList mergeLinkLists(LinkList l1, LinkList l2) {
LinkList l3 = NULL;
Node *p = NULL;
Node *node = NULL;
Node *headNode = l1;
while (l1->next && l2->next) {
if (l1->next->data <= l2->next->data) {
// 需要移动的node
node = l1->next;
l1->next = node->next; // 删除这个node
// 相等时特殊处理
if (node->data == l2->next->data) {
l2 = l2->next;
}
}
else {
node = l2->next;
l2->next = node->next;
}
node->next = NULL; // 斩断联系
if (l3 == NULL) {
l3 = node;
p = node;
}
else {
p->next = node;
p = node;
}
}
// 有一个链表已经没有元素了,则将剩下的直接拼到p的后面就可以了,同时要记得把l1和l2后面斩断
node = l1->next ? l1->next : l2->next;
p->next = node;
l1->next = NULL;
l2->next = NULL;
headNode->next = l3;
l3 = headNode;
return l3;
}
// 测试代码
int main() {
LinkList l1 = initLinkList();
printf("l1遍历--->");
foreachLinkList(l1);
LinkList l2 = initLinkList();
printf("l2遍历--->");
foreachLinkList(l2);
// 合并链表
LinkList l3 = mergeLinkLists(l1, l2);
printf("l3遍历--->");
foreachLinkList(l3);
while (l2) {
Node *fNode = l2;
l2 = l2->next;
fNode->data = 0;
fNode->next = NULL;
free(fNode);
}
return 0;
}
题目二
已知两个链表A和B分别表示两个集合。其元素递增排列列。 设计一个算法,⽤用于求出A与B的交集,并存储在A链表中; 例如: La = {2,4,6,8}; Lb = {4,6,8,10}; Lc = {4,6,8}
题目分析:
求交集和和上一个题目合并有序链表刚好相反。
代码:
// 求两个链表的交集,和链表合并刚好相反
LinkList intersection(LinkList l1, LinkList l2) {
LinkList l3 = NULL;
Node *p = NULL;
while (l1->next && l2->next) {
Node *node = NULL;
// 相等
if (l1->next->data == l2->next->data) {
node = l1->next;
// 往后移一步
l1->next = node->next;
l2->next = l2->next->next;
}
else {
Node *cNode = (l1->next->data < l2->next->data) ? l1 : l2;
cNode->next = cNode->next->next;
}
if (node) {
node->next = NULL;
if (l3 == NULL) {
l3 = p = node;
}
else {
p->next = node;
p = p->next;
}
}
}
return l3;
}
// 测试代码
int main() {
// 创建链表
LinkList l1 = initLinkList();
printf("l1遍历--->");
foreachLinkList(l1);
LinkList l2 = initLinkList();
printf("l2遍历--->");
foreachLinkList(l2);
// 求交集
LinkList l3 = intersection(l1, l2);
printf("l3遍历--->");
Node *p = l3;
while (p) {
printf("%-5d", p->data);
p = p->next;
}
printf("\n");
return 0;
}
题目3
设计⼀个算法,将链表中所有节点的链接方向"原地旋转",即要求仅利用原表的存储空间。换句话说,要求算法空间复杂度为O(1); 例如:L={0,2,4,6,8,10}, 逆转后: L = {10,8,6,4,2,0};
题目分析:
我们可以定义3个指针变量(ps:别说什么不能开辟空间,这里指的是使用链表原本的空间,即不开辟额外的堆空间,定义几个指针还是可以的),front、now、next,分别代表前一个节点,当前的这个节点,下一个节点。
然后遍历链表,将now->next = front
,再接着将front、now和next依次往后移一个即可实现。
代码如下:
// 反转链表
void InvertLinkList(LinkList *ll) {
Node *front = NULL;
Node *now = (*ll)->next;
Node *next = now ? now->next : NULL;
while (now) {
now->next = front;
// 往后移
front = now;
now = next;
next = next ? next->next : NULL;
}
(*ll)->next = front;
}
// 测试代码
int main() {
// 原链表
LinkList ll = initLinkList();
printf("原链表:\n");
foreachLinkList(ll);
// 反转链表
InvertLinkList(&ll);
printf("反链表:\n");
foreachLinkList(ll);
return 0;
}
题目四
设计一个算法,删除递增有序链表中值大于等于mink且小于等于maxk(mink,maxk是给定的两个参数,其值可以和表中的元素相同,也可以不不同)的所有元素。
题目分析:
由于此题目让删除的是一个区间,因此我们只需要找到链表中要删除的第一个节点和要删除的最后一个节点即可。注意,有可能区间最大值比链表最大值大,或者区间最小值比链表最小值小。
代码:
void deleteNode(LinkList *ll, int mink, int maxk) {
Node *p = *ll; // 要删除的第一个节点的前一个
Node *q = NULL; // 要删除的最后一个节点
Node *r = (*ll)->next;
while (r) {
if (r->data < mink) {
p = r;
}
if (!q && r->next && r->next->data > maxk) {
q = r;
}
// 如果q还没找到,并且链表已经循环完了
if (!q && r->next == NULL) {
q = r;
}
r = r->next;
}
// 保存要释放的链表
LinkList fl = p->next;
// 删除链表段
p->next = q->next;
// 断了fl和后面没有删除的链表之间的联系
q->next = NULL;
// 释放链表
freeLinkList(&fl);
}
// 测试代码
int main() {
LinkList ll = initLinkList();
printf("原链表:\n");
foreachLinkList(ll);
// 删除某一段数据
int min = 0, max = 0;
printf("请输入min和max,注意min<=max\n");
scanf("%d%d", &min, &max);
deleteNode(&ll, min, max);
printf("删后表:\n");
foreachLinkList(ll);
return 0;
}
题目五
设将n(n>1)个整数存放到一维数组R中,试设计一个在时间和空间两方面都尽可能高效的算法;将R中保存的序列列循环左移p个位置(0<p<n)个位置
即将R中的数据由(x0,x1,......,xn-1)变换为(xp,xp+1,...,xn-1,x0,x1,...,xp-1)。
例如: pre[10] = {0,1,2,3,4,5,6,7,8,9}, n = 10,p = 3; pre[10] = {3,4,5,6,7,8,9,0,1,2}
题目分析:
这里的思路是将需要偏移的数据存储到另一个数组中,当i >= p
时,将i位置的数据赋值到i-p
的位置,当i >= n-p
时,再将另一个数组保存的前p个数据赋值到原数组中。
空间复杂度:。
时间复杂度:。
int main() {
int n, p;
printf("请输入n和p的值,注意0<p<n\n");
scanf("%d%d", &n, &p);
int r[n];
for (int i = 0; i < n; i++) {
r[i] = n - i;
printf("%-5d", r[i]);
}
printf("\n");
// 向左偏移
int a[p];
for (int i = 0; i < n; i++) {
if (i < p) {
a[i] = r[i];
}
if (i >= p) {
r[i - p] = r[i];
}
if (i >= n - p) {
r[i] = a[i + p - n];
}
}
// 打印数组
for (int i = 0; i < n; i++) {
printf("%-5d", r[i]);
}
printf("\n");
return 0;
}
题目六
已知一个整数序列列A = (a0,a1,a2,...an-1),其中(0<= ai <=n),(0<= i<=n). 若存在ap1= ap2 = ...= apm = x,且m>n/2(0<=pk<n,1<=k<=m),则称x 为 A的主元素。
例如:
A = (0, 5, 5, 3, 5, 7, 5, 5),则5是主元素。
B = (0, 5, 5, 3, 5, 1, 5, 7),则B中没有主元素。
假设A中的n个元素保存在一个一维数组中,请设计一个尽可能高效的算法,找出数组元素中的主元素,若存在主元素则输出该元素,否则输出-1。
题目分析:
所谓主元素,其实就是在数组中重复次数超过数组总元素数量的一半的元素。
代码:
int main() {
int n;
printf("请输入数组元素个数:\n");
scanf("%d", &n);
int a[n];
printf("请输入数组元素的值:\n");
for (int i = 0; i < n; i++) {
scanf("%d", &a[i]);
}
// 遍历
for (int i = 0; i < n; i++) {
printf("%-5d", a[i]);
}
printf("\n\n");
// 求主要元素
int b[n];
for (int i = 0; i < n; i++) {
b[i] = 0; // 默认全部元素都设置为0
}
for (int i = 0; i < n; i++) {
b[a[i]]++;
}
int maxCount = 0;
int maxCountNum = 0;
for (int i = 0; i < n; i++) {
if (b[i] > maxCount) {
maxCount = b[i];
maxCountNum = i;
}
}
if (maxCount > n / 2) {
printf("主要元素是:%d\n", maxCountNum);
}
else {
printf("没有主要元素:-1\n");
}
return 0;
}
题目七
用单链表保存m个整数, 结点的结构为(data,link),且|data|<=n(n为正整数)。 现在要去设计一个时间复杂度尽可能高效的算法。
对于链表中的data绝对值相等的节点, 仅保留留第一次出现的节点,而 删除其余绝对值相等的节点。
例如:链表A = {21,-15,15,-7,15}, 删除后的链表A={21,-15,-7};
题目分析:
注意:时间复杂度尽可能高效,没说空间复杂度😁。
我们可以创建一个数组,用来保存每一个绝对值出现的次数,如果已经出现过了,则就删除这个节点。
代码如下:
// 获取绝对值
int absoluteValue(int a) {
return (a >= 0) ? a : -a;
}
int main() {
int n = 20;
LinkList ll = initLinkList(n);
foreachLinkList(ll);
// 初始化计数数组
int a[n];
for (int i = 0; i < n; i++) {
a[i] = 0;
}
// 遍历链表
Node *p = ll->next;
Node *q = ll;
while (p) {
if (a[absoluteValue(p->data)] > 0) {
q->next = p->next;
p->data = 0;
p->next = NULL;
free(p);
p = q->next;
}
else {
a[absoluteValue(p->data)]++;
q = p;
p = p->next;
}
}
foreachLinkList(ll);
return 0;
}
总结
额,这里不知道写啥了,就练习了一下线性表呗。😂
本文地址https://juejin.cn/post/6844904150078078984