数据结构 C语言版_严蔚敏,李冬梅,吴伟民编著--线性表课后习题
线性表
需要有顺序表和链表的基本了解,这里不再赘述,如有错误欢迎指正。
一、算法设计题目
- 将两个递增的有序链表合并为一个递增的有序链表。要求结果链表仍使用原来两个链表 的存储空间,不另外占用其他的存储空间。表中不允许有重复的数据。
- 将两个非递减的有序链表合并为一个非递增的有序链表。要求结果链表仍使用原来两个 链表的存储空间,不另外占用其他的存储空间。表中允许有重复的数据。
- 已知两个链表 A 和 B 分别表示两个集合,其元素递增排列。请设计一个算法,用于求出A 与 B 的交集,并存放在 A 链表中。
- 巳知两个链表 A 和 B 分别表示两个集合,其元素递增排列。请设计算法求出两个集合 A 和 B 的差集(即仅由在 A 中出现而不在 B 中出现的元素所构成的集合),并以同样的形式存储, 同时返回该集合的元素个数。
- 设计算法将一个带头结点的单链表 A 分解为两个具有相同结构的链表 B 和 C, 其中 B 表的结点为 A 表中值小于零的结点,而 C 表的结点为 A 表中值大于零的结点(链表 A 中的元素 为非零整数,要求 B 、 C 表利用 A 表的结点)。
- 设计一个算法,通过一趟遍历确定长度为 n 的单链表中值最大的结点。
- 设计一个算法,将链表中所有结点的链接方向“原地”逆转,即要求仅利用原表的存储 空间,换句话说,要求算法的空间复杂度为 0(1) 。
- 设计一个算法,删除递增有序链表中值大于 mink 且小于 maxk:的所有元素( mink 和 maxk 是给定的两个参数,其值可以和表中的元素相同,也可以不同)。
- 巳知p指向双向循环链表中的一个结点,其结点结构为 data、prior 、 next三个域,写出算法 change(p),交换p所指向的结点及其前驱结点的顺序。
- 已知长度为 n 的线性表 A 采用顺序存储结构,请写一个时间复杂度为 O(n) 、空间复杂度为 0(1)的算法,该算法可删除线性表中所有值为item 的数据元素。
二、具体实现
1.基本结构和操作
注:以下链表皆带头结点。
-
单链表(带头结点)基本结构和基本操作
/** * \brief 数据域类型 */ typedef int ElemType; /** * \brief 单链表 */ typedef struct Node { ElemType data;//数据域 struct Node* next;//指向下一结点 } Node,*LinkedList; /** * \brief 初始化链表(单链表) * \param list * \return */ int init(LinkedList* list) { (*list) = (Node*)malloc(sizeof(Node)); if (!(*list)) { printf("链表初始化失败!"); return 0; } (*list)->next = NULL; (*list)->data = 0; return 1; } /** * \brief 销毁链表(单链表) * \param list * \return */ int destroy(LinkedList* list) { Node* temp = (*list)->next; while (temp) { Node* next = temp->next; free(temp); temp = next; } free((*list)); (*list)=NULL; return 1; } /** * \brief 头插法(单链表) * \param list 链表 * \param e 元素 * \return */ int insert_head(const LinkedList* list,ElemType e) { Node* createNode = (Node*)malloc(sizeof(Node)); if (!createNode) { printf("头插法新增结点失败!"); return 0; } createNode->data = e; createNode->next = (*list)->next; (*list)->next = createNode; return 1; } /** * \brief 尾插法(单链表) * \param list 链表 * \param e 元素 * \return 是否成功 */ int insert_tail(const LinkedList* list, ElemType e) { Node* createNode = (Node*)malloc(sizeof(Node)); if (!createNode) { printf("尾插法新增结点失败!"); return 0; } Node* temp = (*list); while (temp->next) { temp = temp->next; } createNode->next = temp->next; temp->next = createNode; createNode->data = e; return 1; } /** * \brief 打印链表 * \param list * \param msg */ void display(const LinkedList* list, const char* msg) { printf("打印%s:\n", msg); const Node* temp = (*list)->next; while (temp) { printf("%d ", temp->data); temp = temp->next; } printf("\n"); } /** * \brief 计算带头结点的链表的长度 * \param list 带头结点的链表 * \return 链表的长度 */ int list_length(const LinkedList list) { const Node* temp = list->next; int len = 0; while (temp) { len++; temp = temp->next; } return len; }
-
顺序表基本结构和操作
/** * \brief 顺序表最容量 */ #define MAXSIZE 100 typedef struct SeqList { ElemType data[MAXSIZE]; unsigned int len; }SeqList; /** * \brief 初始化 * \param list 顺序表 * \return 是否成功 */ int init_seq_list(SeqList* list) { for (int i = 0; i < MAXSIZE; i++) { list->data[i] = 0;//将所有数据元素设置为默认 } list->len = 0;//顺序表初始长度为0 return 1; } /** * \brief 插入(顺序表) * \param list 顺序表 * \param data 元素 */ void insert_seq_list(SeqList* list,ElemType data) { if (list->len>=MAXSIZE) { printf("顺序表已满"); return; } list->data[(list->len)++]=data; }
-
双向循环链表基本结构和操作
/** * \brief 双向循环链表 */ typedef struct DCNode { ElemType data;//数据域 struct DCNode* prior;//前驱 struct DCNode* next;//后继 } DCNode, * DCLinkedList; /** * \brief 双向循环链表初始(带头结点,头结点存储INT_MIN值) * \param list 双向链表 * \return 是否成功 */ int initDCLinkedList(DCLinkedList* list) { (*list) = (DCNode*)malloc(sizeof(DCNode)); if (!(*list)) { printf("双向循环链表头结点创建失败"); return 0; } (*list)->data = INT_MIN; (*list)->next = (*list); (*list)->prior = (*list); return 1; } /** * \brief 销毁双向循环链表 * \param list 双向链表 */ void destroyDCLinkedList(DCLinkedList* list) { if (*list == NULL) { return; } DCNode* current = (*list)->next; while (current != (*list)) { DCNode* temp = current; current = current->next; free(temp); } free(*list); *list = NULL; } /** * \brief 头插法(双向循环链表) * \param list 双向循环链表 * \param data 数据 * \return 成功或失败 */ int insert_head_dc(DCLinkedList* list,ElemType data) { DCNode* createNode = (DCNode*)malloc(sizeof(DCNode)); if (!createNode) { printf("新增双向循环链表结点失败"); return 0; } createNode->data = data; createNode->prior = (*list); createNode->next = (*list)->next; (*list)->next->prior = createNode; (*list)->next = createNode; return 1; } /** * \brief 尾插法(双向循环链表) * \param list 双向循环链表 * \param data 数据 * \return 是否成功 */ int insert_tail_dc(DCLinkedList* list, ElemType data) { DCNode* createNode = (DCNode*)malloc(sizeof(DCNode)); if (!createNode) { printf("新增双向循环链表结点失败"); return 0; } createNode->data = data; createNode->next = *list; createNode->prior = (*list)->prior; (*list)->prior->next = createNode; (*list)->prior = createNode; return 1; } /** * \brief 打印双向循环链表 * \param list 双向循环链表 * \param msg 信息 */ void displayDCL(const DCLinkedList* list, const char* msg) { printf("打印%s:\n", msg); const DCNode* temp = (*list)->next; while (temp != (*list)) { printf("%d ", temp->data); temp = temp->next; } printf("\n"); }
2.代码示例
-
将两个递增的有序链表合并为一个递增的有序链表。要求结果链表仍使用原来两个链表 的存储空间,不另外占用其他的存储空间。表中不允许有重复的数据。
/** * \brief 将两个递增的有序链表合并为一个递增的有序链表。 * 要求结果链表仍使用原来两个链表 的存储空间,不另外占用其他的存储空间。表中不允许有重复的数据。 * \note * 如果要实现不占用额外的空间来合并两个有序链表,我们需要在原有链表的基础上进行操作。\n * 我们可以遍历两个链表,依次比较节点的值,然后将较小值的节点插入到结果链表中。具体步骤如下:\n * 1.用一个新指针 result 指向两个链表的头节点中较小的那个。\n * 2.使用两个指针 p1 和 p2 分别遍历两个链表,比较节点的值,并将较小值的节点连接到结果链表上。\n * 3.遍历结束后,将剩余链表(如果有的话)直接链接到结果链表的末尾。 * \param l1 链表1 * \param l2 链表2 * \return 是否成功 */ void merge_increase(LinkedList list1,LinkedList list2) { Node* p1 = list1->next; Node* p2 = list2->next; Node* result = list1; // 新链表的头节点 Node* tail = result; // 新链表的尾节点 while (p1 != NULL && p2 != NULL) { if (p1->data < p2->data) { tail->next = p1; p1 = p1->next; } else if (p1->data > p2->data) { tail->next = p2; p2 = p2->next; } else { // 如果两个节点值相等,只插入一个节点 tail->next = p1; p1 = p1->next; p2 = p2->next; } tail = tail->next; } // 将剩余链表链接到结果链表的末尾 if (p1 != NULL) { tail->next = p1; } if (p2 != NULL) { tail->next = p2; } // 释放 list2 的头节点 free(list2); }
-
将两个非递减的有序链表合并为一个非递增的有序链表。要求结果链表仍使用原来两个 链表的存储空间,不另外占用其他的存储空间。表中允许有重复的数据。
/** * \brief 将两个非递减的有序链表合并为一个非递增的有序链表。 * 要求结果链表仍使用原来两个 链表的存储空间,不另外占用其他的存储空间。表中允许有重复的数据。 * \param list1 * \param list2 */ void merge_decrement(LinkedList list1,LinkedList list2) { Node* p1 = list1->next; Node* p2 = list2->next; Node* head = list1; Node* tail = head; while (p1&&p2) { if (p1->data<p2->data) { tail->next = p2; p2 = p2->next; } else if (p1->data>p2->data) { tail->next = p1; p1 = p1->next; } else { tail->next = p1; p1 = p1->next; tail = tail->next; tail->next = p2; p2 = p2->next; } tail = tail->next; } if (p1) { tail->next = p1; } if (p2) { tail->next = p2; } free(list2); }
-
已知两个链表 A 和 B 分别表示两个集合,其元素递增排列。请设计一个算法,用于求出A 与 B 的交集,并存放在 A 链表中。
/** * \brief 已知两个链表 A 和 B 分别表示两个集合,其元素递增排列。请设计一个算法,用于求出 A 与 B 的交集,并存放在 A 链表中。 * \note * 这个算法的核心思想是使用两个指针 pA 和 pB 分别指向链表 A 和 B 的头节点,然后同时遍历这两个链表。\n * 在遍历过程中,我们比较 pA->data 和 pB->data 的值:\n * 1.如果 pA->data < pB->data,说明链表 A 中的当前节点的值比链表 B 中的当前节点的值小, * 这意味着链表 A 中的当前节点不在交集中,我们需要将其从链表 A 中删除,并释放该节点的内存,然后将 pA 指针向后移动一位;\n * 2.如果 pA->data > pB->data,说明链表 B 中的当前节点的值比链表 A 中的当前节点的值小, * 这意味着链表 B 中的当前节点不在交集中,我们只需要将 pB 指针向后移动一位;\n * 3.如果 pA->data == pB->data,说明链表 A 和 B 中的当前节点的值相等,这意味着该值属于交集, * 我们将其添加到结果链表中(即链表 A),然后同时将 pA 和 pB 指针向后移动一位。\n * 在遍历结束后,链表 A 中存放的就是 A 和 B 的交集,而链表 B 中不属于交集的节点已经被释放。 * \param A 链表A * \param B 链表B */ void intersection(LinkedList A, LinkedList B) { Node* pA = A->next; Node* pB = B->next; Node* prev = A; // 用于记录交集链表的尾节点 while (pA != NULL && pB != NULL) { if (pA->data < pB->data) { // pA 指向的值小于 pB 指向的值,将 pA 指针向后移动 Node* temp = pA; pA = pA->next; free(temp); // 释放 A 链表中不在交集中的节点 } else if (pA->data > pB->data) { // pA 指向的值大于 pB 指向的值,pB 指针向后移动 Node* temp = pB; pB = pB->next; free(temp); // 释放 B 链表中不在交集中的节点 } else { // pA 指向的值等于 pB 指向的值,将该值添加到结果链表中,并同时移动 pA 和 pB prev->next = pA; prev = pA; pA = pA->next; Node* temp = pB; pB = pB->next; free(temp); // 释放 B 链表中重复的节点 } } // 处理 A 链表剩余的节点,将其置为 NULL prev->next = NULL; // 释放 B 链表剩余的节点 while (pB != NULL) { Node* temp = pB; pB = pB->next; free(temp); } // 释放链表 B 的头节点 free(B); }
-
巳知两个链表 A 和 B 分别表示两个集合,其元素递增排列。请设计算法求出两个集合 A 和 B 的差集(即仅由在 A 中出现而不在 B 中出现的元素所构成的集合),并以同样的形式存储, 同时返回该集合的元素个数。
/** * \brief 巳知两个链表 A 和 B 分别表示两个集合,其元素递增排列。 * 请设计算法求出两个集合 A 和 B 的差集(即仅由在 A 中出现而不在 B 中出现的元素所构成的集合),并以同样的形式存储,同时返回该集合的元素个数。 * \param A 链表 A * \param B 链表 B * \return 差集元素的个数 */ int difference(LinkedList A, LinkedList B) { Node* pA = A->next; // 指向链表 A 的第一个节点 Node* pB = B->next; // 指向链表 B 的第一个节点 Node* prev = A; // 用于记录差集链表的尾节点的前一个节点 Node* tail = prev; // 用于记录差集链表的尾节点 int count = 0; // 差集元素的个数 // 遍历链表 A 和链表 B,构建差集链表 while (pA && pB) { if (pA->data < pB->data) { // 如果链表 A 的当前节点的值小于链表 B 的当前节点的值, // 则将链表 A 的当前节点插入到差集链表中 tail->next = pA; pA = pA->next; tail = tail->next; // 更新差集链表的尾节点 count++; // 差集元素个数加一 } else if (pA->data > pB->data) { // 如果链表 A 的当前节点的值大于链表 B 的当前节点的值, // 则只需要将链表 B 的当前节点丢弃并释放即可 Node* tempB = pB; pB = pB->next; free(tempB); } else { // 如果链表 A 和链表 B 的当前节点的值相等, // 则需要将两个链表的当前节点都丢弃 Node* tempA = pA; tail->next = pA->next; // 从差集链表中删除链表 A 的当前节点 pA = pA->next; // 移动链表 A 的当前节点指针 free(tempA); // 释放链表 A 的当前节点的内存 Node* tempB = pB; pB = pB->next; // 移动链表 B 的当前节点指针 free(tempB); // 释放链表 B 的当前节点的内存 } } // 将链表 A 剩余的节点添加到差集链表中 while (pA) { tail->next = pA; pA = pA->next; tail = tail->next; count++; // 差集元素个数加一 } // 释放链表 B 头结点的内存 free(B); // 返回差集元素的个数 return count; }
-
设计算法将一个带头结点的单链表 A 分解为两个具有相同结构的链表 B 和 C, 其中 B 表的结点为 A 表中值小于零的结点,而 C 表的结点为 A 表中值大于零的结点(链表 A 中的元素 为非零整数,要求 B 、 C 表利用 A 表的结点)。
/** * \brief * 设计算法将一个带头结点的单链表 A 分解为两个具有相同结构的链表 B 和 C, * 其中 B 表的结点为 A 表中值小于零的结点, * 而 C 表的结点为 A 表中值大于零的结点(链表 A 中的元素为非零整数,要求 B 、 C 表利用 A 表的结点)。 * \param A 单链表 A * \param B 单链表 B * \param C 单链表 C */ void split_list(LinkedList A, LinkedList* B, LinkedList* C) { // 初始化链表 B 和链表 C init(B); init(C); Node* p = A->next; Node* pB = *B; // 指向链表 B 的尾节点 Node* pC = *C; // 指向链表 C 的尾节点 // 遍历链表 A,根据节点的值将节点插入到链表 B 或链表 C while (p != NULL) { if (p->data < 0) { // 将节点插入到链表 B 中 pB->next = p; pB = pB->next; } else { // 将节点插入到链表 C 中 pC->next = p; pC = pC->next; } p = p->next; } // 结束链表 B 和链表 C pB->next = NULL; pC->next = NULL; free(A); }
6.设计一个算法,通过一趟遍历确定长度为 n 的单链表中值最大的结点。
/**
* \brief 设计一个算法,通过一趟遍历确定长度为 n 的单链表中值最大的结点。
* \param list 带头结点的单链表
* \param n 带头结点单链表的长度
* \return 值最大的节点
*/
Node* find_max_node(const LinkedList list,int n)
{
if (list == NULL || n <= 0) {
return NULL;
}
Node* max_node = list->next; // 假设第一个节点是值最大的节点
// 遍历单链表,找到值最大的节点
Node* current = list->next;
int count = 1;
while (current != NULL && count <= n) {
if (current->data > max_node->data) {
max_node = current; // 更新最大节点
}
current = current->next;
count++;
}
return max_node;
}
-
设计一个算法,将链表中所有结点的链接方向“原地”逆转,即要求仅利用原表的存储 空间,换句话说,要求算法的空间复杂度为 0(1) 。
/** * \brief 设计一个算法,将链表中所有结点的链接方向“原地”逆转,即要求仅利用原表的存储 空间,换句话说,要求算法的空间复杂度为 0(1) 。 * \param list 单链表 */ void reverse(LinkedList* list) { //如果链表为空直接返回 if (!(*list)->next||!(*list)) { return; } Node* current = (*list)->next; Node* next = current->next; while (next) { current->next = next->next; next->next = (*list)->next; (*list)->next = next; next = current->next; } }
-
设计一个算法,删除递增有序链表中值大于 mink 且小于 maxk:的所有元素( mink 和 maxk 是给定的两个参数,其值可以和表中的元素相同,也可以不同)。
/** * \brief 设计一个算法,删除递增有序链表中值大于 mink 且小于 maxk:的所有元素( mink 和 maxk 是给定的两个参数,其值可以和表中的元素相同,也可以不同)。 * \note * 该算法可以分为以下几个步骤:\n 1.初始化一个指针(我们可以称之为prev),让它指向链表的头结点。这样可以方便地处理要删除的节点是链表中第一个节点的情况。\n 2.使用另一个指针(称为current)遍历链表,从头结点的下一个节点开始(即链表的第一个实际数据节点)。\n 3.检查current指针指向的节点的data值是否在mink和maxk之间。如果是,就删除这个节点,同时保持prev指针不变,将current指针移动到下一个节点。\n 4.如果current指针指向的节点的data值不在mink和maxk之间,则两个指针同时向后移动。\n 5.重复步骤3和4,直到current指针到达链表的末尾。\n 优化:\n 1.提前终止:当当前节点的值大于等于maxk时,可以立即停止遍历,因为后续的所有节点都不会满足删除条件。\n 提前终止的条件:当当前节点的值大于或等于maxk时,循环会停止。这意味着,一旦遇到一个节点的值不小于maxk, 函数就不会继续遍历链表的剩余部分,从而提高效率。这在处理长链表时尤其有用,可以减少对链表尾部不满足条件节点的不必要遍历。\n * \param list 链表 * \param mink 最小值 * \param maxk 最大值 */ void deleteRange(LinkedList* list, ElemType mink, ElemType maxk) { if (mink > maxk) { printf("参数错误!\n"); return; } if ((*list) == NULL || (*list)->next == NULL) return; Node* prev = (*list), * current = (*list)->next; while (current != NULL && current->data < maxk) { // 提前终止条件 if (current->data > mink) { prev->next = current->next; // 删除当前节点 free(current); current = prev->next; // 移动到下一个节点 } else { prev = current; // 如果当前节点的值不满足删除条件,移动prev current = current->next; // 移动current } } }
-
巳知p指向双向循环链表中的一个结点,其结点结构为 data、prior 、 next三个域,写出算法 change(p),交换p所指向的结点及其前驱结点的顺序。
/** * \brief 巳知p指向双向循环链表中的一个结点,其结点结构为 data、prior 、 next三个域,写出 算法 change(p),交换p所指向的结点及其前驱结点的顺序。 * \param p */ void change(DCNode* p) { //如果是空表或者只有一个结点不需要交换 if (p->prior == p || p->next == p||p->data==INT_MIN) { return; } DCNode* pPrior = p->prior; // p的前驱节点 DCNode* pPriorPrior = pPrior->prior; // p的前驱的前驱节点 DCNode* pNext = p->next; // p的后继节点 //如果前驱结点是头结点,跳过头结点,反之不需要 if (pPrior->data==INT_MIN) { //pPriorPrior的前驱结点的后继指向p pPriorPrior->prior->next = p; p->prior = pPriorPrior->prior; p->next = pPrior; pPrior->prior = p; pPrior->next = pPriorPrior; pPriorPrior->prior = pPrior; pPriorPrior->next = pNext; //pNext的前驱结点的后继指向p pNext->prior = pPriorPrior; } else { // 将p的前驱的前驱节点的next指向p pPriorPrior->next = p; p->prior = pPriorPrior; // 将p的后继节点的prior指向p的前驱 pNext->prior = pPrior; pPrior->next = pNext; // 将p与其前驱节点相互连接 p->next = pPrior; pPrior->prior = p; } }
-
已知长度为 n 的线性表 A 采用顺序存储结构,请写一个时间复杂度为 O(n) 、空间复杂度为 0(1)的算法,该算法可删除线性表中所有值为item 的数据元素。
/** * \brief 已知长度为 n 的线性表 A 采用顺序存储结构,请写一个时间复杂度为 O(n) 、空间复杂 度为 0(1)的算法,该算法可删除线性表中所有值为item 的数据元素。 * \param list * \param item */ void delSame(SeqList* list,ElemType item) { int index = 0; for (unsigned int i = 0; i < list->len; i++) { if (list->data[i] != item) { list->data[index++] = list->data[i]; } } list->len = index; }