数据结构与算法 线性表习题
题目:
将2个递增的有序链表合并为一个有序链表; 要求结果链表仍然使用两个链表的存储空间,不另外占用其他的存储空间. 表中不允许有重复的数据
关键词:递增有序链表,不允许有重复数据,保留递增关系(后插法)
不占用额外的存储空间指的是不能开辟新节点,赋值在链接到链表上;
int combine(LinkList *L1, LinkList *L2, LinkList* C){
LinkList p1 = *L1;
LinkList p2 = *L2;
LinkList pc;
p1 = p1 -> next;
p2 = p2 -> next;
*C = *L1;//合并后的数组
pc = *L1;
while (p1 && p2) {
if (p1 -> data < p2 -> data) { //如果第一个链表的结点小于第二个链表的结点
pc -> next = p1;
p1 = p1 -> next;
pc = pc -> next;
}else if (p1 -> data > p2 -> data) {//第二个链表的结点小于第一个链表的结点
pc -> next = p2;
p2 = p2 -> next;
pc = pc -> next;
}else{//相等这种情况,需要去释放其中一个结点
pc -> next = p1;
p1 = p1 -> next;
LinkList tem = p2;//用一个临时变量记录这个存在于L2中的结点p2
free(p2);//释放结点p2
p2 = tem -> next;//将p2指向tem的下一个结点
pc = pc -> next;
}
}
if (p1 == NULL) { //p1遍历完毕了
pc -> next = p2;
}
if (p2 == NULL) {//p2遍历完毕了
pc -> next = p1;
}
free(*L2);//释放第二个数组的头结点
return 1;
}
int main(int argc, const char * argv[]) {
// insert code here...
printf("Hello, World!\n");
LinkList L1;
LinkList L2;
createList(&L1);
traverse(L1);
createList(&L2);
traverse(L2);
LinkList LC;
combine(&L1, &L2, &LC);
traverse(LC);
return 0;
}
题目:
已知两个链表A和B分别表示两个集合.其元素递增排列. 设计一个算法,用于求出A与B的交集,并存储在A链表中;
例如:
La = {2,4,6,8}; Lb = {4,6,8,10};
Lc = {4,6,8}
关键词:依次摘取2个表中相等的元素重新进行链接,删除其他不等的元素;
int Intersection(LinkList* LA, LinkList* LB, LinkList* LC){
LinkList p1 = *LA;
LinkList p2 = *LB;
LinkList temp;
p1 = p1->next;//从LA链的首元结点开始
p2 = p2->next;//从LB链的首元结点开始
LinkList p3;
*LC = *LA;//*LC是指向LA链的头结点
p3 = *LC;//临时变量p3是用来记录*LC的结点跳跃
while (p1 && p2) {
if (p1 -> data == p2 -> data) {//如果相等
p3 -> next = p1;
temp = p2;
p2 = temp -> next;
free(temp);
p1 = p1 ->next;
p3 = p3 -> next;
}else if (p1 -> data < p2 -> data){//如果第一个链结点小于第二个链的结点
temp = p1;
p1 = temp -> next;
free(temp);
}else{//若果第二个链结点小于第一个链结点
temp = p2;
p2 = temp -> next;
free(temp);
}
}
while (p1) { //去除LA其他结点
temp = p1;
p1 = temp -> next;
free(temp);
}
while (p2) {//去除LB其他结点
temp = p2;
p2 = temp -> next;
free(temp);
}
p3 -> next = NULL;
free(*LB);
return 1;
}
题目:
设计一个算法,将链表中所有节点的链接方向"原地旋转",即要求仅仅利用原表的存储空间. 换句话说,要求算法空间复杂度为O(1);
例如:L={0,2,4,6,8,10}, 逆转后: L = {10,8,6,4,2,0};
关键词:不能开辟新的空间,只能改变指针的指向; 可以考虑逐个摘取结点,利用前插法创建链表的思想,将结点一次插入到头结点的后面; 因为先插入的结点为表尾,后插入的结点为表头,即可实现链表的逆转;
//仔细看这个循环比如对于一个链表{1, 2, 3, 4, 5}
//第一次遍历的时候 p = 1, tem保存的是结点2
// 调用 p -> next = (*L) -> next 即 p -> next = NULL,
//调用 (*L) -> next = p; 即 *L -> next 指向 1结点
//第二次遍历 p = 2, tem 保存结点3
// 调用 p -> next = (*L) -> next 即 p -> next 指向于 1这个结点,
//调用 (*L) -> next = p; 即 *L -> next 指向 2结点 依次往复循环
int InverseList(LinkList* L){
LinkList p = (*L) -> next;
LinkList tem;
(*L) -> next = NULL;
while (p) {
tem = p -> next;//tem指针保存的p->next
p -> next = (*L) -> next;//间隔p->next指向与头结点的next 即 这个结点p 他要进入首元结点的位置,首先要做的就是 p->next与*L->next链接起来
(*L) -> next = p;//将头结点的的next指向这个拿到的结点,即该节点成为首元结点
p = tem;//开始处理最早保存好的下一个结点
}
return 1;
}
题目:
设计一个算法,删除递增有序链表中值大于等于mink且小于等于maxk(mink,maxk是给定的两个参数,其值可以和表中的元素相同,也可以不同)的所有元素;
关键词: 通过遍历链表能够定位带删除元素的下边界和上边界, 即可找到第一个值大于mink的结点和第一个值大于等于maxk的结点;
int deleteMinMax(LinkList* L, int min, int max){
LinkList p, q, pre;
p = (*L) -> next;
q = (*L) -> next;
pre = *L;
//1.查找第一值大于mink的结点
while (p) {
if (p -> data < min){
pre = p;
p = p->next;
}else{
break;
}
}
//2.查找第一个值大于等于maxk的结点
while (q) {
if (q -> data <= max) {
q = q -> next;
}else{
break;
}
}
//3.修改待删除的结点指针
p = pre -> next;
pre -> next = q;
while (p != q) {//释放被删除的结点
LinkList temp = p;
p = p -> next;
free(temp);
}
return 1;
}
设将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}
算法思路:
- 先将n个数据原地逆置 9,8,7,6,5,4,3,2,1,0;
- 将n个数据拆解成[9,8,7,6,5,4,3] [2,1,0]
- 将前n-p个数据和后p个数据分别原地逆置; [3,4,5,6,7,8,9] [0,1,2]
int reverseArray(int* array,int left,int right){
int i = left, j = right;
int temp;
while (i < j) {
temp = array[i];
array[i] = array[j];
array[j] = temp;
i++;
j--;
}
return 1;
}
void leftShift(int* array, int length, int p){
if (p < length && p > 0) {
reverseArray(array, 0, length - 1);
reverseArray(array, 0, length - p - 1);
reverseArray(array, length - p, length);
}
}
题目:
已知一个整数序列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.
题目分析:
主元素,是数组中的出现次数超过一半的元素; 当数组中存在主元素时,所有非主元素的个数和必少于一半. 如果让主元素和一个非主元素配对, 则最后多出来的元素(没有元素与之匹配就是主元素.
算法思路:
- 选取候选主元素, 从前向后依次扫描数组中的每个整数, 假定第一个整数为主元素,将其保存在Key中,计数为1. 若遇到下一个整数仍然等于key,则计数加1. 否则计数减1. 当计数减到0时, 将遇到的下一个整数保存到key中, 计数重新记为1. 开始新一轮计数. 即可从当前位置开始重上述过程,直到将全部数组元素扫描一遍;
- 判断key中的元素是否是真正的主元素, 再次扫描数组, 统计key中元素出现的次数,若大于n/2,则为主元素,否则,序列中不存在主元素;
算法分析:
时间复杂度: O(n)
空间复杂度: O(1)
int mainElement(int* array, int length){
// int* tempArray = {0};
int mainElement = array[0];
int count = 1;
for (int i = 1; i < length; i++) {//因为默认count = 1, 所以从第二位开始比较
if (array[i] == mainElement) {
count ++;
}else{
if (count > 0) {
count --; //不想等,减一
}else{
mainElement = array[i];//更换新的主元素
count = 1;//更换主元素默认为count = 1,从下一位开始比较
}
}
}
// int count = 0;
if (count > 0) {
count = 0;
for (int i = 0; i < length; i++) {
if (mainElement == array[i]) {//开始计算数量
count++;
}
}
}
if (count > length/2) {//判断是否满足条件
return mainElement;
}
return -1;
}
题目:
用单链表保存m个整数, 结点的结构为(data,link),且|data|<=n(n为正整数). 现在要去设计一个时间复杂度尽可能高效的算法. 对于链表中的data 绝对值相等的结点, 仅保留第一次出现的结点,而删除其余绝对值相等的结点.例如,链表A = {21,-15,15,-7,15}, 删除后的链表A={21,-15,-7};
题目分析:
要求设计一个时间复杂度尽量高效的算法,而已知|data|<=n, 所以可以考虑用空间换时间的方法. 申请一个空间大小为n+1(0号单元不使用)的辅助数组. 保存链表中已出现的数值,通过对链表进行一趟扫描来完成删除.
算法思路:
- 申请大小为n+1的辅助数组t并赋值初值为0;
- 从首元结点开始遍历链表,依次检查t[|data|]的值, 若[|data|]为0,即结点首次出现,则保留该结点,并置t[|data|] = 1,若t[|data|]不为0,则将该结点从链表中删除.
复杂度分析:
时间复杂度: O(m),对长度为m的链表进行一趟遍历,则算法时间复杂度为O(m);
空间复杂度: O(n)
int DeleteEqualNode(LinkList* L, int temp_array_length){
int* temp_array = malloc(sizeof(int) * temp_array_length);
for (int i = 0; i < temp_array_length; i++) {
*(temp_array + i) = 0;
}
LinkList p = (*L) -> next;
LinkList tem = p;
while (p) {
if (temp_array[abs(p->data)] == 1) {//绝对值序号的值为1,证明已经来过了
tem -> next = p -> next; // 直接将当前结点temp的next 指向 他的下一个结点p的next
free(p);
p = tem -> next;//继续记录当前结点的next,即p
}else{
temp_array[abs(p->data)] = 1;//第一次进来对数组赋值1
tem = p; //第一次进来,记录当前结点tem
p = tem -> next;//记录当前结点的next,即p
}
}
return 1;
}