学有所用
通过前几天对线性表的学习,用几道算法题对线性表的这次学习进行收尾。每个人的思维方式不同,写的算法代码也不同。以下的相关代码,是本人感觉思路比较清晰简洁的方式。如你有自己的想法,可以自己尝试一下,希望可以将你的方式分享出来给我,谢谢!
1.将2个递增的有序链表合并为一个链表的有序链表;要求结果链表仍然使用两个链表的存储空间,不另外占用其他的存储空间。表中不允许有重复的数据
1.1分析已知条件
- 两个递增有序链表list1、list2
- 不使用额外的存储空间
- list3中不允许有重复数据
- 相当于求两个链表的并集
1.2思路
- 同时遍历两个链表
- 取出两个链表中相对小的结点
- 如果相等,取list1中的结点,释放list2中的结点
- 取到的结点插入到list3中(链表后插法)
1.3代码实现
Status mergeOrderedLists(LinkList *list1, LinkList *list2, LinkList *list3) {
LinkList p1 = (*list1)->next;
LinkList p2 = (*list2)->next;
LinkList p3 = *list1;
*list3 = p3;
LinkList temp;
//循环遍历两个列表
while (p1 && p2) {
if (p1->data < p2->data) {//如果链表1中的结点data小于链表2中结点data,链表1结点插入到链表3中
p3->next = p1;
p3 = p1;
p1 = p1->next;
} else if (p1->data > p2->data) {//如果链表1中的结点data大于链表2中结点data,链表2结点插入到链表3中
p3->next = p2;
p3 = p2;
p2 = p2->next;
} else {//如果链表1中的结点data等于链表2中结点data,保留链表1的结点,free链表2的结点
p3->next = p1;
p3 = p1;
p1 = p1->next;
temp = p2->next;
free(p2);
p2 = temp;
}
}
//退出循环的条件是链表1或者链表2同时指向NULL或者其中一个指向NULL,用一个三则运算符,把剩下的结点插入到链表3中。
p3->next = p1?p1:p2;
free(*list2);
return OK;
}
int main(int argc, const char * argv[]) {
// insert code here...
printf("Hello, World!\n");
LinkList list1;
LinkList list2;
createList(&list1);
createList(&list2);
for (int i = 1; i < 10; i += 2) {
insertValue(&list1, i);
}
insertValue(&list2, 1);
insertValue(&list2, 2);
insertValue(&list2, 3);
insertValue(&list2, 4);
printf("list1:");
displayList(list1);
printf("\n");
printf("list2:");
displayList(list2);
printf("\n");
LinkList list3;
mergeOrderedLists(&list1, &list2, &list3);
displayList(list3);
return 0;
}
1.4运行结果
==================分割线==================
2.已知两个链表A和B分别表示两个集合。其元素递增排列。设计一个算法,用于求出A和B的交集,并存储在A链表中;例如:La={2,4,6,8};Lb={4,6,8,10};Lc={4,6,8}
2.1分析已知条件
- 两个递增有序链表list1、list2
- 不占用额外的存储空间
- list3中存储两个链表的交集结点
- 相当于求两个链表的交集
2.2思路
- p1和p2指向两个链表首元结点,开始遍历。
- p1->data == p2->data
- 保存p1结点到list3中,释放p2结点
- 不相等,删除较小的结点,并向下遍历
- 如果有一个链表遍历到头了,删除另一个链表的后续结点
2.3代码实现
Status orderedListIntersection(LinkList *list1, LinkList *list2, LinkList *list3) {
LinkList p1 = (*list1)->next;
LinkList p2 = (*list2)->next;
LinkList p3 = *list1;
*list3 = p3;
LinkList temp;
while (p1 && p2) {
if (p1->data == p2->data) {//相等,把p1加入的list3中,释放p2。p1和p2都往下指向
p3->next = p1;
p3 = p1;
p1 = p1->next;
temp = p2->next;
free(p2);
p2 = temp;
} else if (p1->data < p2->data){//p1小,释放p1,p1向下指向
temp = p1->next;
free(p1);
p1 = temp;
} else {//p2小,释放p2,p2向下指向
temp = p2->next;
free(p2);
p2 = temp;
}
}
while (p1) {//p1后面还有结点,循环删除
temp = p1->next;
free(p1);
p1 = temp;
}
while (p2) {//p2后面还有结点,循环删除
temp = p2->next;
free(p2);
p2 = temp;
}
//p3链表结尾设为NULL
p3->next = NULL;
free(*list2);
return OK;
}
int main(int argc, const char * argv[]) {
LinkList list1;
LinkList list2;
createList(&list1);
insertValue(&list1, 1);
insertValue(&list1, 2);
insertValue(&list1, 3);
insertValue(&list1, 4);
insertValue(&list1, 5);
insertValue(&list1, 6);
insertValue(&list1, 7);
insertValue(&list1, 8);
insertValue(&list1, 9);
printf("list1\n");
displayList(list1);
createList(&list2);
insertValue(&list2, 2);
insertValue(&list2, 4);
insertValue(&list2, 6);
insertValue(&list2, 8);
insertValue(&list2, 10);
printf("list2\n");
displayList(list2);
LinkList list3;
orderedListIntersection(&list1, &list2, &list3);
printf("list3\n");
displayList(list3);
return 0;
}
2.4运行结果
==================分割线==================
3.设计一个算法,将链表中所有节点的链接方向”原地旋转“,即要求仅仅利用原表的存储空间。换句话说,要求算法空间复杂度为O(1)。例如:L={0,2,4,6,8,10},逆转后:L={10,8,6,4,2,0}
3.1分析已知条件
- 原地旋转链表
- 不能使用额外的存储空间,空间复杂度为O(1)
3.2思路
- 链表头插法
- p指向链表的首元结点,遍历链表
- 取出p指向的结点,插入到链表的头结点后面
3.3代码实现
Status listReverseOrder(LinkList *list) {
LinkList p = (*list)->next;
LinkList q;
(*list)->next = NULL;
while (p) {
q = p->next;
p->next = (*list)->next;
(*list)->next = p;
p = q;
}
return OK;
}
int main(int argc, const char * argv[]) {
LinkList list;
createList(&list);
for (int i = 0; i <= 10; i+=2) {
insertValue(&list, i);
}
displayList(list);
listReverseOrder(&list);
displayList(list);
return 0;
}
3.4运行结果
==================分割线==================
4.设计一个算法,删除递增有序链表中值大于等于mink且小于等于maxk(mink,maxk是给定的两个参数,其值可以和表中的元素相同,也可以不同)的所有元素
4.1分析已知条件
- 递增有序链表
- 删除mink-maxk之间的值,包含等于
4.2思路
- 找最小值的前一个结点
- 找到最大值的结点
- 循环删除找到的范围内的结点
4.3代码实现
Status OrderedListDelElemByRange(LinkList *list, ElemType min, ElemType max) {
if (*list == NULL) {
return ERROR;
}
LinkList p = (*list)->next;
LinkList low = NULL, high = NULL;
//找到小于min的结点的前一个结点low
while (p && p->data < min) {
low = p;
p = p->next;
}
//找到小于等于max的结点high
while (p && p->data <= max) {
p = p->next;
}
high = p;
//原链表剔除low->next到high之间的结点
low->next = high;
//删除low->next到high之间的结点
LinkList temp;
while (low != high) {
temp = low->next;
free(low);
low = temp;
}
return OK;
}
int main(int argc, const char * argv[]) {
LinkList list;
createList(&list);
for (int i = 1; i < 100; i++) {
insertValue(&list, i);
}
displayList(list);
OrderedListDelElemByRange(&list, 8, 24);
displayList(list);
return 0;
}
4.4运行结果
==================分割线==================
5.设将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}
5.1分析已知条件
- 是数组,不是链表
- 时间和空间两个方面尽可能高效
- 数组循环左移
5.2思路
- 倒叙原数组{9,8,7,6,5,4,3,2,1,0}
- 找到分割点,分割数组{(9,8,7,6,5,4,3),(2,1,0)}
- 再分别倒叙分割点的前部分和后部分{(3,4,5,6,7,8,9),(0,1,2)}
5.3代码实现
void reverse(int *arr,int left ,int right){
int low = left;
int high = right;
while (low < high) {
arr[low] ^= arr[high] ^= arr[low] ^= arr[high];
low ++;
high --;
}
}
void leftShift(int *arr, int n, int p){
if (p > 0 && p < n) {
//整体翻转
reverse(arr, 0, n - 1);
//翻转分割点前面部分
reverse(arr, 0, n - p - 1);
//翻转分割点后面部分
reverse(arr, n-p, n-1);
}
}
int main(int argc, const char * argv[]) {
int arr[10] = {0,1,2,3,4,5,6,7,8,9};
for (int i = 0; i < 10; i++) {
printf("%d ", arr[i]);
}
printf("\n");
leftShift(arr, 10, 3);
for (int i = 0; i < 10; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
5.4运行结果
==================分割线==================
6.已知一个整数序列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),则A 中没有主元素,假设A中的n个元素保存在一个一维数组中,请设计一个尽可能高效的算法,找出数组元素中的主元素,若存在主元素则输出该元素,否则输出-1.
6.1分析已知条件
- 找主元素
- 元素出现的次数大于数组长度的一半
6.2思路
- 先找候选主元素
- key = 第一个元素, count = 1
- 循环数组
- 下一个元素与key相等,count++
- 下一个元素与key不相等,并且count > 0,count
- 如果count < 0,key = 当前元素,count = 1
- 如果count > 0
- 循环找这个元素出现的次数,用count表示
- count大于数组的length/2 返回key 否则返回-1
6.3代码实现
int getMainElement(int *arr, int n){
//把第一个元素当做候选主元素
int key = arr[0];
int count = 1;
for (int i = 1; i < n; i++) {
if (key == arr[i]) {
count ++;
} else {
if (count > 0) {
count --;
} else {
key = arr[i];
count = 1;
}
}
}
//count > 0 ,统计候选主元素出现的次数,用count表示
if (count > 0) {
for (int i = count = 0; i < n; i++) {
if (arr[i] == key) {
count ++;
}
}
}
//判断count是否满足大于数组一半的要求
if (count > n / 2) {
return key;
} else {
return -1;
}
}
int main(int argc, const char * argv[]) {
int a[] = {0,5,5,3,5,7,5,5};
int b[] = {0,5,5,3,5,1,5,7};
int c[] = {0,1,2,3,4,5,6,7};
for (int i = 0; i < 8; i++) {
printf("%d ", a[i]);
}
printf("\n");
int value = getMainElement(a, 8);
printf("value = %d \n", value);
value = getMainElement(b, 8);
printf("value = %d \n", value);
value = getMainElement(c, 8);
printf("value = %d \n", value);
return 0;
}
6.4运行结果
==================分割线==================
7.用单链表保存m个整数, 结点的结构为(data,link),且|data|<=n(n为正整数). 现在要去设计一个时间复杂度尽可能高效的算法. 对于链表中的data 绝对值相等的结点, 仅保留第一次出现的结点,而删除其余绝对值相等的结点.例如,链表A = {21,-15,15,-7,15}, 删除后的链表A={21,-15,-7};
7.1分析已知条件
- 时间复杂度尽可能高的算法
- 空间换时间
- 申请一个空间大小为n+1(0号单元不使用)的辅助数组
- 数组中保存链表中出现的值
- 这样,一次循环就可以达到目的
7.2思路
- 申请大小为n+1的辅助数组
- 遍历链表
- 若数组中 |data| = 0 说明第一次出现 并赋值1
- 若不等于 0 说明已经出现过,删除结点
7.3代码实现
void listDelRepeatElem(LinkList *list,int n){
//开辟辅助数组
int *arr = alloca(sizeof(int)*n);
for (int i = 0; i < n; i++) {
*(arr+i) = 0;
}
LinkList p = (*list)->next;
LinkList temp = *list;
//遍历链表
while (p) {
//如果该绝对值已经在结点上出现过,则删除该结点
if (arr[abs(p->data)] == 1) {
temp->next = p->next;
free(p);
p = temp->next;
} else {//未出现过的结点,则将数组中对应位置置为1
arr[abs(p->data)] = 1;
temp = p;
p = p->next;
}
}
}
int main(int argc, const char * argv[]) {
// insert code here...
printf("Hello, World!\n");
LinkList list;
createList(&list);
insertValue(&list, 21);
insertValue(&list, -15);
insertValue(&list, 15);
insertValue(&list, -7);
insertValue(&list, 15);
displayList(list);
listDelRepeatElem(&list, 21);
displayList(list);
return 0;
}
7.4运行结果
==================分割线==================
总结
线性表告一段落,接下来还会继续学习数据结构相关知识。继续享受学习过程的快乐。记住:沿途的风景要比目的地更弯的否!!!