数据结构与算法 总共分为19个系列
①、数据结构与算法[基础]+线性结构部分内容篇
②、单向循环链表的创建插入删除实现篇
③、双向链表和双向循环链表的实现篇
④、数据结构-线性表-算法练习篇
- 数据结构和线性表练习题、简单题
- 线性表算法题
A . 动态结构和静态结构
B . 紧凑结构和非紧凑结构
C .线性结构和非线性结构
D .内部结构和外部结构
C, 解析,数据的逻辑结构是从逻辑关系上描述数据,可以看作从具体问题抽象
出数学模型.根据数据元素之间的关系的不同特性,逻辑结构通常可以划分为
集合结构,线性结构,树结构和图结构.其中集合结构,树结构和图结构都属于
非线性结构.因此逻辑结构可以分为线性结构和非线性结构两大类.
A .存储结构
B .存储实现
C .逻辑结构
D . 运算实现
C,逻辑结构是从具体问题抽象出来的数学模型.从逻辑关系上描述数据,它与
数据的存储无关.也就是说与数据本身的具体形式,内容,相对位置,个数无关.
这意味着
A .数据具有同一特点
B . 不仅数据元素所包含的数据项个数要相同,而对应数据项的类型要一致
C . 每个数据元素都一样
D . 数据元素所包含的数据项的个数要相等.
B, 数据项是组成数据元素的,有独立含义,不可分割的最小单位. 同一个逻辑
结构中的数据元素所包含的数据项的个数要相同,且要求对应的数据项的类型
也要一致.例如,对于学生基本信息表.这种线性表结构,其中数据元素为所有
的学生,数据项是学号,姓名,性别等.这里的学号,姓名,性别的数据类型是一致
的.所以选择B.
A .数据元素是数据最小的单位
B . 数据项是数据的基本单位
C .数据结构是带有结构的各项数据的集合
D .一些表面上很不相同的数据可以相同的数据逻辑结构
D,数据元素是数据的基本单位,数据项是数据的最小单位,数据结构是带有结
构的各数据元素的集合.因此ABC都是错误的. 选择D是正确的,因为逻辑结构
是从逻辑关系上描述数据.它与数据本身的具体形式无关.例如,学生表和图书
表都可以看做线性结构,而学生数据和图书数据表面是完全不同的数据.所以
选择D.
x =2;
while (x < n/2)
x = 2 *x;
A. O(log2n)
B.O(n)
C. 0(nlog2n)
D.O(n^2)
A.程序中基本语句×= 2*x;执行的次数为k,执行k时:x-2k+1.由循环结束
条件 x < n/2,可得出 2k+1
< n/2 ,k = log2 n|C (C为常数),因此 T(n) = O(
logz n:
count = 0;
for(k=1;k<=n;k*=2)
for(j=1;jcount++
A . O(log2n)
B . O(n)
c . O(nlog2n)
D . O(n^2)
注意A为log2n
C,内层循环条件j<= n 与外层循环变量无关,每次循环j自增1,每次内循环都执
行n次. 所以内层循环的时间复杂度是 O(n). 外层循环条件为k<=n,增量定义
为k *= 2,可知循环次数为2k <= n,即k <= log2 n|所以外层循环的时间复杂度
是O(log2 ². 对于嵌套循环,根据乘法法则则可以知道该段程序的时间复杂度
为: O(nlog2²
A . O(n)
B . O(nlog2n)
c . O(n^2)
D . 0(log2n)
注意: D为log2²因为代码段无法打不出来
C,在计算时间复杂度时,可以忽略所有低次幂项和最高次幂项的系数.
A. O(n)
B. 0(nlog2n)
C.O(1)
D.O(n^2)
注意: C为nlog2²因为代码段无法打不出来
C, 读取第i个数组元素可以直接通过数组的下标定位,与n无关,因此平均时间
复杂度为O(1);
for(i=@;i
for(i=0;i
A.0(1)
B.O(n)
C.0(log2n)
D.O(n^2)
注意: C为log2²
因为代码段无法打不出来
B,算法的空间复杂度只需分析算法在实现时所需要的辅助空间和问题规模n
的函数关系.该算法需要另外借助一个大小为n的辅助数组b,所以其空间复杂
度为O(n);
for(i=@;i
.
t=a[i];
a[i]=a[n-i-1];
a[n-i-1]=t;
A.O(1)
B.O(n)
C.O(1og2n)
D.0(n^2)
注意:C为log2²因为代码段无法打不出来
A,该算法仅需要借助一个变量t,与问题规模n大小无关,所以其空间复杂度为
O(1);
A.一个算法的空间复杂度大,则其时间复杂度也必定大;
B.一个算法的空间复杂度大,则其时间复杂度必定小;
C.一个算法的时间复杂度大,则其空间复杂度必定小;
D.上述三种说法都不对;
D,算法的时间复杂度和空间复杂度没有直接关系.
x=90,y=100;
while(y>0)
if(x>100)
x=X-10;
y--;
}else{
x++;
}
O(1),程序中基本语句"y–”,"x++"执行次数是由x和y决定的,而x和y都是一个
常数.所以,T(n) = O(1);
for(i=0;i<n;i++)
for(j=0;j<m;j++)
a[i][j] = 0;
O(nm),由于程序为嵌套循环,外层循环的执行次数为n,内层循环的次数为m,所
以T(n) = O(nm);
s=0;
for(i=0;i<n;i++)
for(j=0;j<n;j++)
s+=B[i][j];
sum =
s;
O(n2).
由于程序为嵌套循环,外层循环的执行次数为n,内层循环的执行次数为
n. 所以T(n) = O(n2).
i = 1;
while(i<=n)
i=i*3;
O(log3²),设基本语句“i=i”3;”的执行次数为f(n),则3
|3 fn <= n. T(n) =
log3²
x=O;
for(i=1;i<n;i++)
for(j=1;j<n-i;j++)
x++;
O(n2) 基本语句"x++"的执行次数为: n-1 + n-2 + …+ 1 = n(n-1)/2. 因
此,T(n) = O(n2);
x=n;//n>1
y=@;
while(x>= (y+1)*(y+1))
y++;
O(sqrt(n)),基本语句"y++"的执行次数为f(n),则x>=(f(n)+1)2.又由于x = n ,所以
f(n) <= sqrt(n) - 1; 即 T(n) = O(sqrt(n));
La {1,2,3} , Lb {3, 6,9}
Lc {1, 2, 3, 6, 9}
关键字:
递增有序链表;
不允许有重复数据
保持递增关系(后插法)
不占⽤额外空间: 额外新建结点维持逻辑
思路:
a.待合并链表 La,Lb, 头指针Lc; (借⽤La的头结点)
b.pa,pb ,
c.循环(La/Lb 都没有达到尾结点位置)
d.判断链表取较⼩者,存储Lc
e.相等La元素插⼊Lc,Lb 释放掉
typedef int Status;/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int ElemType;/* ElemType类型根据实际情况而定,这里假设为int */
//定义结点
typedef struct Node{
ElemType data;
struct Node *next;
}Node;
typedef struct Node * LinkList;
//2.1 初始化单链表线性表
Status InitList(LinkList *L){
//产生头结点,并使用L指向此头结点
*L = (LinkList)malloc(sizeof(Node));
//存储空间分配失败
if(*L == NULL) return ERROR;
//将头结点的指针域置空
(*L)->next = NULL;
return OK;
}
//2.2 单链表插入
/*
初始条件:顺序线性表L已存在,1≤i≤ListLength(L);
操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1;
*/
Status ListInsert(LinkList *L,int i,ElemType e){
int j;
LinkList p,s;
p = *L;
j = 1;
//寻找第i个结点
while (p && j<i) {
p = p->next;
++j;
}
//第i个元素不存在
if(!p || j>i) return ERROR;
//生成新结点s
s = (LinkList)malloc(sizeof(Node));
//将e赋值给s的数值域
s->data = e;
//将p的后继结点赋值给s的后继
s->next = p->next;
//将s赋值给p的后继
p->next = s;
return OK;
}
/* 初始条件:顺序线性表L已存在 */
/* 操作结果:依次对L的每个数据元素输出 */
Status ListTraverse(LinkList L)
{
LinkList p=L->next;
while(p)
{
printf("%d ",p->data);
p=p->next;
}
printf("\n");
return OK;
}
/* 初始条件:顺序线性表L已存在。操作结果:将L重置为空表 */
Status ClearList(LinkList *L)
{
LinkList p,q;
p=(*L)->next; /* p指向第一个结点 */
while(p) /* 没到表尾 */
{
q=p->next;
free(p);
p=q;
}
(*L)->next=NULL; /* 头结点指针域为空 */
return OK;
}
/*
作业1:
题目:
将2个递增的有序链表合并为一个有序链表; 要求结果链表仍然使用两个链表的存储空间,不另外占用其他的存储空间. 表中不允许有重复的数据
关键词:递增有序链表,不允许有重复数据,保留递增关系(后插法)
不占用额外的存储空间指的是不能开辟新节点,赋值在链接到链表上;
算法思想:
(1)假设待合并的链表为La和Lb,合并后的新表使用头指针Lc(Lc的表头结点设为La的表头结点)指向. Pa 和 Pb 分别是La,Lb的工作指针.初始化为相应链表的首元结点
(2)从首元结点开始比较, 当两个链表La 和Lb 均未到达表尾结点时,依次摘取其中较小值重新链表在Lc表的最后.
(3)如果两个表中的元素相等,只摘取La表中的元素,删除Lb表中的元素,这样确保合并后表中无重复的元素;
(4)当一个表达到表尾结点为空时,非空表的剩余元素直接链接在Lc表最后.
(5)最后释放链表Lb的头结点;
*/
void MergeList(LinkList *La, LinkList *Lb, LinkList *Lc){
//目标:将2个递增的有序链表La,Lb 合并为一个递增的有序链表Lc
LinkList pa,pb,pc,temp;
//pa 是链表La的工作指针,pb 是链表Lb的工作指针, 初始化为首元结点;
pa = (*La)->next;
pb = (*Lb)->next;
*Lc = pc = *La;
while (pa && pb) {
if (pa->data < pb->data) {
//取较小者La中的元素,将pa链接在pc的后面,pa指针后移
pc->next = pa;
pc = pa;
pa = pa->next;
}else if(pa->data > pb->data){
//取较小者Lb的元素,将pb链接在pc后面, pb指针后移
pc->next = pb;
pc = pb;
pb = pb->next;
}else
{
//相等时取La中的元素,删除Lb的元素;
pc->next = pa;
pc = pa;
pa = pa ->next;
temp = pb->next;
free(pb);
pb = temp;
}
}
//将非空表的剩余元素之间链接在Lc表的最后
pc->next = pa?pa:pb;
//释放Lb的头结点
free(*Lb);
}
int main(int argc, const char * argv[]) {
// insert code here...
printf("线性表练习篇!\n");
Status iStatus;
LinkList La,Lb,Lc,L;
InitList(&La);
InitList(&Lb);
// ---------作业1--------
printf("******题目1:********\n");
//设计2个递增链表La,Lb
for(int j = 10;j>=0;j-=2)
{
iStatus = ListInsert(&La, 1, j);
}
printf("La:\n");
ListTraverse(La);
for(int j = 11;j>0;j-=2)
{
iStatus = ListInsert(&Lb, 1, j);
}
printf("Lb:\n");
ListTraverse(Lb);
MergeList(&La, &Lb, &Lc);
printf("Lc:\n");
ListTraverse(Lc);
return 0;
}
求出A与 B的交集
,并存储在A链表中;La,Lb,Lc(La 的头结点) pa,pb,⾸元结点
循环条件pa,pb 不为空
相等, La 结点,链接Lc; Lb 结点释放 较⼩ ⼲掉!
不存在交集多余释放
/*
作业2:
题目:
已知两个链表A和B分别表示两个集合.其元素递增排列. 设计一个算法,用于求出A与B的交集,并存储在A链表中;
例如:
La = {2,4,6,8}; Lb = {4,6,8,10};
Lc = {4,6,8}
关键词:依次摘取2个表中相等的元素重新进行链接,删除其他不等的元素;
算法思想:
(1)假设待合并的链表为La和Lb,合并后的新表使用头指针Lc(Lc的表头结点设为La的表头结点)指向. Pa 和 Pb 分别是La,Lb的工作指针.初始化为相应链表的首元结点
(2)从首元结点开始比较, 当两个链表La 和Lb 均未到达表尾结点时.
(3)如果两个表中的元素相等,只摘取La表中的元素,删除Lb表中的元素;
(4)如果其中一个表中的元素较小,删除此表中较小的元素. 此表的工作指针后移;
(5)当链表La和Lb有一个先到达表尾结点为空时,依次删除另一个非空表中的所有元素,最后释放链表lb;
*/
void Intersection(LinkList *La, LinkList *Lb, LinkList *Lc){
//目标: 求2个递增的有序链表La,Lb的交集, 使用头指针Lc指向带回;
LinkList pa,pb,pc,temp;
//pa 是链表La的工作指针,pb 是链表Lb的工作指针, 初始化为首元结点;La的头结点作为Lc的头结点;
pa = (*La)->next;
pb = (*Lb)->next;
*Lc = pc = *La;
while (pa && pb) {
if (pa->data == pb->data) {
//相等,交集并入结果链表中;
//(1).取La中的元素,将pa链接到pc的后面,pa指针后移;
pc->next = pa;
pc = pa;
pa = pa->next;
//(2)删除Lb中对应相等的元素
temp = pb;
pb = pb->next;
free(temp);
}else if(pa->data < pb->data){
//删除较小值La的元素;
temp = pa;
pa = pa->next;
free(temp);
}else{
//删除较小值Lb中的元素
temp = pb;
pb = pb->next;
free(temp);
}
}
//Lb为空,删除非空表La中的所有元素
while (pa) {
temp = pa;
pa = pa->next;
free(temp);
}
//La为空,删除非空表Lb中的所有元素
while (pb) {
temp = pb;
pb = pb->next;
free(temp);
}
pc->next = NULL;
free(*Lb);
}
int main(int argc, const char * argv[]) {
// insert code here...
printf("线性表练习篇!\n");
Status iStatus;
LinkList La,Lb,Lc,L;
InitList(&La);
InitList(&Lb);
/*---------作业2--------*/
printf("******题目2:********\n");
ListInsert(&La, 1, 8);
ListInsert(&La, 1, 6);
ListInsert(&La, 1, 4);
ListInsert(&La, 1, 2);
printf("La:\n");
ListTraverse(La);
ListInsert(&Lb, 1, 10);
ListInsert(&Lb, 1, 8);
ListInsert(&Lb, 1, 6);
ListInsert(&Lb, 1, 4);
printf("Lb:\n");
ListTraverse(Lb);
Intersection(&La, &Lb, &Lc);
printf("Lc:\n");
ListTraverse(Lc);
return 0;
}
原地旋转
,即要求仅仅利⽤原表的存储 空间. 换句话说,要求算法空间复杂度为O(1);例如:L={0,2,4,6,8,10},
逆转后: L = {10,8,6,4,2,0};
思路:
*L 作为新的旋转后的链表⾸结点 p ⾸元结点;
从前往后遍历遍历
临时p 指向待插⼊结点后继结点
/*
作业3:
题目:
设计一个算法,将链表中所有节点的链接方向"原地旋转",即要求仅仅利用原表的存储空间. 换句话说,要求算法空间复杂度为O(1);
例如:L={0,2,4,6,8,10}, 逆转后: L = {10,8,6,4,2,0};
关键词:不能开辟新的空间,只能改变指针的指向; 可以考虑逐个摘取结点,利用前插法创建链表的思想,将结点一次插入到头结点的后面; 因为先插入的结点为表尾,后插入的结点为表头,即可实现链表的逆转;
算法思想:
(1)利用原有的头结点*L,p为工作指针, 初始时p指向首元结点. 因为摘取的结点依次向前插入,为确保链表尾部为空,初始时将头结点的指针域置空;
(2)从前向后遍历链表,依次摘取结点,在摘取结点前需要用指针q记录后继结点,以防止链接后丢失后继结点;
(3)将摘取的结点插入到头结点之后,最后p指向新的待处理节点q(p=q);
*/
void Inverse(LinkList *L){
//目的: 逆转带头结点单链表L;
LinkList p,q;
//p指向首元结点;
p = (*L)->next;
//头结点的指针域置空
(*L)->next = NULL;
//遍历链表
while (p!=NULL) {
//q执行p的后继
q = p->next;
//p->next = (*L)->next
p->next = (*L)->next;
//*p 插入到头结点之后;
(*L)->next = p;
//处理下一个结点
p = q;
}
}
int main(int argc, const char * argv[]) {
// insert code here...
printf("线性表练习篇!\n");
Status iStatus;
LinkList La,Lb,Lc,L;
InitList(&La);
InitList(&Lb);
/*---------作业3--------*/
printf("******题目3:********\n");
InitList(&L);
for(int j = 10;j>=0;j-=2)
{
iStatus = ListInsert(&L, 1, j);
}
printf("L逆转前:\n");
ListTraverse(L);
Inverse(&L);
printf("L逆转后:\n");
ListTraverse(L);
return 0;
}
删除
递增有序链表中值⼤于等于mink且⼩于等于maxk(mink,maxk 是给定的两个参数
,其值可以和表中的元素相同,也可以不同)的所有元素;思路:
遍历链表 第⼀个⼤于minK结点,pre 保存前驱;
遍历链表 第⼀个⼤于maxK结点, p指向;
pre->next = p;
介于pre和p之间结点删除掉
/*
作业4:
题目:
设计一个算法,删除递增有序链表中值大于等于mink且小于等于maxk(mink,maxk是给定的两个参数,其值可以和表中的元素相同,也可以不同)的所有元素;
关键词: 通过遍历链表能够定位带删除元素的下边界和上边界, 即可找到第一个值大于mink的结点和第一个值大于等于maxk的结点;
算法思想:
(1)查找第一个值大于mink的结点,用q指向该结点,pre 指向该结点的前驱结点;
(2)继续向下遍历链表, 查找第一个值大于等于maxk的结点,用p指向该结点;
(3)修改下边界前驱结点的指针域, 是其指向上边界(pre->next = p);
(4)依次释放待删除结点的空间(介于pre和p之间的所有结点);
*/
void DeleteMinMax(LinkList *L, int mink, int maxk){
//目标: 删除递增有序链表L中值大于等于mink 和小于等于maxk的所有元素
LinkList p,q,pre;
pre = *L;
LinkList temp;
//p指向首元结点
p = (*L)->next;
//1.查找第一值大于mink的结点
while (p && p->data < mink) {
//指向前驱结点
pre = p;
p = p->next;
}
//2.查找第一个值大于等于maxk的结点
while (p && p->data<=maxk) {
p = p->next;
}
//3.修改待删除的结点指针
q = pre->next;
pre->next = p;
while (q != p) {
temp = q->next;
free(q);
q = temp;
}
}
int main(int argc, const char * argv[]) {
// insert code here...
printf("线性表练习篇!\n");
Status iStatus;
LinkList La,Lb,Lc,L;
InitList(&La);
InitList(&Lb);
// /*---------作业4--------*/
printf("******题目4:********\n");
InitList(&L);
for(int j = 10;j>=0;j-=2)
{
iStatus = ListInsert(&L, 1, j);
}
printf("L链表:\n");
ListTraverse(L);
DeleteMinMax(&L, 4, 10);
printf("删除链表mink与maxk之间结点的链表:\n");
return 0;
}
循环左移p个位置
(0
思路:
a. n个数据原地逆置 9 8 7 6 5 4 3 2 1 0
b. 拆 解 [9 8 7 6 5 4 3][2 1 0 ]
c. n-p数据/p个数据 再⼀次逆置 [3 4 5 6 7 8 9][0 1 2]
/*
题目5:
设将n(n>1)个整数存放到一维数组R中, 试设计一个在时间和空间两方面都尽可能高效的算法;将R中保存的序列循环左移p个位置(0
void Reverse(int *pre,int left ,int right){
//将数组R中的数据原地逆置
//i等于左边界left,j等于右边界right;
int i = left,j = right;
int temp;
//交换pre[i] 和 pre[j] 的值
while (i < j) {
//交换
temp = pre[i];
pre[i] = pre[j];
pre[j] = temp;
//i右移,j左移
i++;
j--;
}
}
//n 表示 数组的长度
// p 表示 逆序
// 1-10
// n = 10
// p = 3
// 则数据原始为
// 1,2,3,4,5,6,7,8,9,0
// 操作完之后
// 第一次逆序
//[9,8,7,6,5,4,3],[2,1,0]
// 最终要求结果
// 3,4,5,6,7,8,9,0,1,2
void LeftShift(int *pre,int n,int p){
//将长度为n的数组pre 中的数据循环左移p个位置
if (p>0 && p<n) {
//1. 将数组中所有元素全部逆置
Reverse(pre, 0, n-1);
//2. 将前n-p个数据逆置
Reverse(pre, 0, n-p-1);
//3. 将后p个数据逆置
Reverse(pre, n-p, n-1);
}
}
int main(int argc, const char * argv[]) {
// insert code here...
printf("线性表练习篇!\n");
Status iStatus;
LinkList La,Lb,Lc,L;
InitList(&La);
InitList(&Lb);
// /*---------作业5--------*/
printf("******题目5:********\n");
int pre[10] = {0,1,2,3,4,5,6,7,8,9};
LeftShift(pre, 10, 3);
for (int i=0; i < 10; i++) {
printf("%d ",pre[i]);
}
printf("\n");
return 0;
}
题目分析:
主元素: 数组中出现次数超过一半的元素;
思路:
1.选择 候选主元素,循环计数方式找到链表哪个元素出现次数多!认为候选人
2.循环目的:候选人到底出现次数是多少
3.判断到底是不是候选人,候选人 count > n - 2
/*
题目6:
已知一个整数序列A = (a0,a1,a2,...an-1),其中(0<= ai <=n),(0<= i<=n). 若存在ap1= ap2 = ...= apm = x,且m>n/2(0<=pk
int MainElement(int *A, int n){
//目标: 求整数序列A中的主元素;
//count 用来计数
int count = 1;
//key 用来保存候选主元素, 初始A[0]
int key = A[0];
//(1) 扫描数组,选取候选主元素
for (int i = 1; i < n; i++) {
//(2) 如果A[i]元素值 == key ,则候选主元素计数加1;
if (A[i] == key) {
count++;
}else{
//(3) 当前元素A[i] 非候选主元素,计数减1;
if(count >0){
count--;
}else{
//(4) 如果count 等于0,则更换候选主元素,重新计数
key = A[i];
count = 1;
}
}
}
//如果count >0
if (count >0){
//(5)统计候选主元素的实际出现次数
for (int i = count = 0; i < n; i++)
if (A[i] == key) count++;
}
//(6)判断count>n/2, 确认key是不是主元素
if (count > n/2) return key;
else return -1; //不存在主元素
}
int main(int argc, const char * argv[]) {
// insert code here...
printf("线性表练习篇!\n");
Status iStatus;
LinkList La,Lb,Lc,L;
InitList(&La);
InitList(&Lb);
// /*---------作业6--------*/
printf("******题目6:********\n");
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};
int value = MainElement(A, 8);
printf("数组A 主元素为: %d\n",value);
value = MainElement(B, 8);
printf("数组B 主元素为(-1表示数组没有主元素): %d\n",value);
value = MainElement(C, 8);
printf("数组C 主元素为(-1表示数组没有主元素): %d\n",value);
return 0;
}
data 绝对值相等的结点,
仅保留 第⼀次出现
的结点,⽽删除其余绝对值相等的结点.例如,链表A = {21,-15,15,-7,15}, 删除后 的链表A={21,-15,-7};算法思路:
题目告诉了我们n
- 申请n+1 辅助空间数组t,赋初值为0.
- 首元结点开始遍历,
t[|data|] = 0, 保留这个结点
t[|data|] = 1,若 t[|data|] = 1,删除结点
0 没有出现过
1 表示出现过
时间复杂度 : O(m) m指链表长度
空间复杂度 : O(n) n指的链表绝对值最大的数
/*
题目7:
用单链表保存m个整数, 结点的结构为(data,link),且|data|<=n(n为正整数). 现在要去设计一个时间复杂度尽可能高效的算法. 对于链表中的data 绝对值相等的结点, 仅保留第一次出现的结点,而删除其余绝对值相等的结点.例如,链表A = {21,-15,15,-7,15}, 删除后的链表A={21,-15,-7};
题目分析:
要求设计一个时间复杂度尽量高效的算法,而已知|data|<=n, 所以可以考虑用空间换时间的方法. 申请一个空间大小为n+1(0号单元不使用)的辅助数组. 保存链表中已出现的数值,通过对链表进行一趟扫描来完成删除.
算法思路:
1. 申请大小为n+1的辅助数组t并赋值初值为0;
2. 从首元结点开始遍历链表,依次检查t[|data|]的值, 若[|data|]为0,即结点首次出现,则保留该结点,并置t[|data|] = 1,若t[|data|]不为0,则将该结点从链表中删除.
复杂度分析:
时间复杂度: O(m),对长度为m的链表进行一趟遍历,则算法时间复杂度为O(m);
空间复杂度: O(n)
*/
void DeleteEqualNode(LinkList *L,int n){
//目标: 删除单链表中绝对值相等的结点;
//1. 开辟辅助数组p.
int *p = alloca(sizeof(int)*n);
LinkList r = *L;
//2.数组元素初始值置空
for (int i = 0; i < n; i++) {
*(p+i) = 0;
}
//3.指针temp 指向首元结点
LinkList temp = (*L)->next;
//4.遍历链表,直到temp = NULL;
while (temp!= NULL) {
//5.如果该绝对值已经在结点上出现过,则删除该结点
if (p[abs(temp->data)] == 1) {
//临时指针指向temp->next
r->next = temp->next;
//删除temp指向的结点
free(temp);
//temp 指向删除结点下一个结点
temp = r->next;
}else
{
//6. 未出现过的结点,则将数组中对应位置置为1;
p[abs(temp->data)] = 1;
r = temp;
//继续向后遍历结点
temp = temp->next;
}
}
}
int main(int argc, const char * argv[]) {
// insert code here...
printf("线性表练习篇!\n");
Status iStatus;
LinkList La,Lb,Lc,L;
InitList(&La);
InitList(&Lb);
// /*---------作业7--------*/
//21,-15,15,-7,15
printf("******题目7:********\n");
InitList(&L);
ListInsert(&L, 1, 21);
ListInsert(&L, 1, -15);
ListInsert(&L, 1, 15);
ListInsert(&L, 1, -7);
ListInsert(&L, 1, 15);
DeleteEqualNode(&L, 21);
ListTraverse(L);
return 0;
}
#include <stdio.h>
#include "string.h"
#include "ctype.h"
#include "stdlib.h"
#include "math.h"
#include "time.h"
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OK 1
#define MAXSIZE 20 /* 存储空间初始分配量 */
typedef int Status;/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int ElemType;/* ElemType类型根据实际情况而定,这里假设为int */
//定义结点
typedef struct Node{
ElemType data;
struct Node *next;
}Node;
typedef struct Node * LinkList;
//2.1 初始化单链表线性表
Status InitList(LinkList *L){
//产生头结点,并使用L指向此头结点
*L = (LinkList)malloc(sizeof(Node));
//存储空间分配失败
if(*L == NULL) return ERROR;
//将头结点的指针域置空
(*L)->next = NULL;
return OK;
}
//2.2 单链表插入
/*
初始条件:顺序线性表L已存在,1≤i≤ListLength(L);
操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1;
*/
Status ListInsert(LinkList *L,int i,ElemType e){
int j;
LinkList p,s;
p = *L;
j = 1;
//寻找第i个结点
while (p && j<i) {
p = p->next;
++j;
}
//第i个元素不存在
if(!p || j>i) return ERROR;
//生成新结点s
s = (LinkList)malloc(sizeof(Node));
//将e赋值给s的数值域
s->data = e;
//将p的后继结点赋值给s的后继
s->next = p->next;
//将s赋值给p的后继
p->next = s;
return OK;
}
/* 初始条件:顺序线性表L已存在 */
/* 操作结果:依次对L的每个数据元素输出 */
Status ListTraverse(LinkList L)
{
LinkList p=L->next;
while(p)
{
printf("%d ",p->data);
p=p->next;
}
printf("\n");
return OK;
}
/* 初始条件:顺序线性表L已存在。操作结果:将L重置为空表 */
Status ClearList(LinkList *L)
{
LinkList p,q;
p=(*L)->next; /* p指向第一个结点 */
while(p) /* 没到表尾 */
{
q=p->next;
free(p);
p=q;
}
(*L)->next=NULL; /* 头结点指针域为空 */
return OK;
}
/*
作业1:
题目:
将2个递增的有序链表合并为一个有序链表; 要求结果链表仍然使用两个链表的存储空间,不另外占用其他的存储空间. 表中不允许有重复的数据
关键词:递增有序链表,不允许有重复数据,保留递增关系(后插法)
不占用额外的存储空间指的是不能开辟新节点,赋值在链接到链表上;
算法思想:
(1)假设待合并的链表为La和Lb,合并后的新表使用头指针Lc(Lc的表头结点设为La的表头结点)指向. Pa 和 Pb 分别是La,Lb的工作指针.初始化为相应链表的首元结点
(2)从首元结点开始比较, 当两个链表La 和Lb 均未到达表尾结点时,依次摘取其中较小值重新链表在Lc表的最后.
(3)如果两个表中的元素相等,只摘取La表中的元素,删除Lb表中的元素,这样确保合并后表中无重复的元素;
(4)当一个表达到表尾结点为空时,非空表的剩余元素直接链接在Lc表最后.
(5)最后释放链表Lb的头结点;
*/
void MergeList(LinkList *La, LinkList *Lb, LinkList *Lc){
//目标:将2个递增的有序链表La,Lb 合并为一个递增的有序链表Lc
LinkList pa,pb,pc,temp;
//pa 是链表La的工作指针,pb 是链表Lb的工作指针, 初始化为首元结点;
pa = (*La)->next;
pb = (*Lb)->next;
*Lc = pc = *La;
while (pa && pb) {
if (pa->data < pb->data) {
//取较小者La中的元素,将pa链接在pc的后面,pa指针后移
pc->next = pa;
pc = pa;
pa = pa->next;
}else if(pa->data > pb->data){
//取较小者Lb的元素,将pb链接在pc后面, pb指针后移
pc->next = pb;
pc = pb;
pb = pb->next;
}else
{
//相等时取La中的元素,删除Lb的元素;
pc->next = pa;
pc = pa;
pa = pa ->next;
temp = pb->next;
free(pb);
pb = temp;
}
}
//将非空表的剩余元素之间链接在Lc表的最后
pc->next = pa?pa:pb;
//释放Lb的头结点
free(*Lb);
}
/*
作业2:
题目:
已知两个链表A和B分别表示两个集合.其元素递增排列. 设计一个算法,用于求出A与B的交集,并存储在A链表中;
例如:
La = {2,4,6,8}; Lb = {4,6,8,10};
Lc = {4,6,8}
关键词:依次摘取2个表中相等的元素重新进行链接,删除其他不等的元素;
算法思想:
(1)假设待合并的链表为La和Lb,合并后的新表使用头指针Lc(Lc的表头结点设为La的表头结点)指向. Pa 和 Pb 分别是La,Lb的工作指针.初始化为相应链表的首元结点
(2)从首元结点开始比较, 当两个链表La 和Lb 均未到达表尾结点时.
(3)如果两个表中的元素相等,只摘取La表中的元素,删除Lb表中的元素;
(4)如果其中一个表中的元素较小,删除此表中较小的元素. 此表的工作指针后移;
(5)当链表La和Lb有一个先到达表尾结点为空时,依次删除另一个非空表中的所有元素,最后释放链表lb;
*/
void Intersection(LinkList *La, LinkList *Lb, LinkList *Lc){
//目标: 求2个递增的有序链表La,Lb的交集, 使用头指针Lc指向带回;
LinkList pa,pb,pc,temp;
//pa 是链表La的工作指针,pb 是链表Lb的工作指针, 初始化为首元结点;La的头结点作为Lc的头结点;
pa = (*La)->next;
pb = (*Lb)->next;
*Lc = pc = *La;
while (pa && pb) {
if (pa->data == pb->data) {
//相等,交集并入结果链表中;
//(1).取La中的元素,将pa链接到pc的后面,pa指针后移;
pc->next = pa;
pc = pa;
pa = pa->next;
//(2)删除Lb中对应相等的元素
temp = pb;
pb = pb->next;
free(temp);
}else if(pa->data < pb->data){
//删除较小值La的元素;
temp = pa;
pa = pa->next;
free(temp);
}else{
//删除较小值Lb中的元素
temp = pb;
pb = pb->next;
free(temp);
}
}
//Lb为空,删除非空表La中的所有元素
while (pa) {
temp = pa;
pa = pa->next;
free(temp);
}
//La为空,删除非空表Lb中的所有元素
while (pb) {
temp = pb;
pb = pb->next;
free(temp);
}
pc->next = NULL;
free(*Lb);
}
/*
作业3:
题目:
设计一个算法,将链表中所有节点的链接方向"原地旋转",即要求仅仅利用原表的存储空间. 换句话说,要求算法空间复杂度为O(1);
例如:L={0,2,4,6,8,10}, 逆转后: L = {10,8,6,4,2,0};
关键词:不能开辟新的空间,只能改变指针的指向; 可以考虑逐个摘取结点,利用前插法创建链表的思想,将结点一次插入到头结点的后面; 因为先插入的结点为表尾,后插入的结点为表头,即可实现链表的逆转;
算法思想:
(1)利用原有的头结点*L,p为工作指针, 初始时p指向首元结点. 因为摘取的结点依次向前插入,为确保链表尾部为空,初始时将头结点的指针域置空;
(2)从前向后遍历链表,依次摘取结点,在摘取结点前需要用指针q记录后继结点,以防止链接后丢失后继结点;
(3)将摘取的结点插入到头结点之后,最后p指向新的待处理节点q(p=q);
*/
void Inverse(LinkList *L){
//目的: 逆转带头结点单链表L;
LinkList p,q;
//p指向首元结点;
p = (*L)->next;
//头结点的指针域置空
(*L)->next = NULL;
//遍历链表
while (p!=NULL) {
//q执行p的后继
q = p->next;
//p->next = (*L)->next
p->next = (*L)->next;
//*p 插入到头结点之后;
(*L)->next = p;
//处理下一个结点
p = q;
}
}
/*
作业4:
题目:
设计一个算法,删除递增有序链表中值大于等于mink且小于等于maxk(mink,maxk是给定的两个参数,其值可以和表中的元素相同,也可以不同)的所有元素;
关键词: 通过遍历链表能够定位带删除元素的下边界和上边界, 即可找到第一个值大于mink的结点和第一个值大于等于maxk的结点;
算法思想:
(1)查找第一个值大于mink的结点,用q指向该结点,pre 指向该结点的前驱结点;
(2)继续向下遍历链表, 查找第一个值大于等于maxk的结点,用p指向该结点;
(3)修改下边界前驱结点的指针域, 是其指向上边界(pre->next = p);
(4)依次释放待删除结点的空间(介于pre和p之间的所有结点);
*/
void DeleteMinMax(LinkList *L, int mink, int maxk){
//目标: 删除递增有序链表L中值大于等于mink 和小于等于maxk的所有元素
LinkList p,q,pre;
pre = *L;
LinkList temp;
//p指向首元结点
p = (*L)->next;
//1.查找第一值大于mink的结点
while (p && p->data < mink) {
//指向前驱结点
pre = p;
p = p->next;
}
//2.查找第一个值大于等于maxk的结点
while (p && p->data<=maxk) {
p = p->next;
}
//3.修改待删除的结点指针
q = pre->next;
pre->next = p;
while (q != p) {
temp = q->next;
free(q);
q = temp;
}
}
/*
题目5:
设将n(n>1)个整数存放到一维数组R中, 试设计一个在时间和空间两方面都尽可能高效的算法;将R中保存的序列循环左移p个位置(0
void Reverse(int *pre,int left ,int right){
//将数组R中的数据原地逆置
//i等于左边界left,j等于右边界right;
int i = left,j = right;
int temp;
//交换pre[i] 和 pre[j] 的值
while (i < j) {
//交换
temp = pre[i];
pre[i] = pre[j];
pre[j] = temp;
//i右移,j左移
i++;
j--;
}
}
void LeftShift(int *pre,int n,int p){
//将长度为n的数组pre 中的数据循环左移p个位置
if (p>0 && p<n) {
//1. 将数组中所有元素全部逆置
Reverse(pre, 0, n-1);
//2. 将前n-p个数据逆置
Reverse(pre, 0, n-p-1);
//3. 将后p个数据逆置
Reverse(pre, n-p, n-1);
}
}
/*
题目6:
已知一个整数序列A = (a0,a1,a2,...an-1),其中(0<= ai <=n),(0<= i<=n). 若存在ap1= ap2 = ...= apm = x,且m>n/2(0<=pk
int MainElement(int *A, int n){
//目标: 求整数序列A中的主元素;
//count 用来计数
int count = 1;
//key 用来保存候选主元素, 初始A[0]
int key = A[0];
//(1) 扫描数组,选取候选主元素
for (int i = 1; i < n; i++) {
//(2) 如果A[i]元素值 == key ,则候选主元素计数加1;
if (A[i] == key) {
count++;
}else{
//(3) 当前元素A[i] 非候选主元素,计数减1;
if(count >0){
count--;
}else{
//(4) 如果count 等于0,则更换候选主元素,重新计数
key = A[i];
count = 1;
}
}
}
//如果count >0
if (count >0){
//(5)统计候选主元素的实际出现次数
for (int i = count = 0; i < n; i++)
if (A[i] == key) count++;
}
//(6)判断count>n/2, 确认key是不是主元素
if (count > n/2) return key;
else return -1; //不存在主元素
}
/*
题目7:
用单链表保存m个整数, 结点的结构为(data,link),且|data|<=n(n为正整数). 现在要去设计一个时间复杂度尽可能高效的算法. 对于链表中的data 绝对值相等的结点, 仅保留第一次出现的结点,而删除其余绝对值相等的结点.例如,链表A = {21,-15,15,-7,15}, 删除后的链表A={21,-15,-7};
题目分析:
要求设计一个时间复杂度尽量高效的算法,而已知|data|<=n, 所以可以考虑用空间换时间的方法. 申请一个空间大小为n+1(0号单元不使用)的辅助数组. 保存链表中已出现的数值,通过对链表进行一趟扫描来完成删除.
算法思路:
1. 申请大小为n+1的辅助数组t并赋值初值为0;
2. 从首元结点开始遍历链表,依次检查t[|data|]的值, 若[|data|]为0,即结点首次出现,则保留该结点,并置t[|data|] = 1,若t[|data|]不为0,则将该结点从链表中删除.
复杂度分析:
时间复杂度: O(m),对长度为m的链表进行一趟遍历,则算法时间复杂度为O(m);
空间复杂度: O(n)
*/
void DeleteEqualNode(LinkList *L,int n){
//目标: 删除单链表中绝对值相等的结点;
//1. 开辟辅助数组p.
int *p = alloca(sizeof(int)*n);
LinkList r = *L;
//2.数组元素初始值置空
for (int i = 0; i < n; i++) {
*(p+i) = 0;
}
//3.指针temp 指向首元结点
LinkList temp = (*L)->next;
//4.遍历链表,直到temp = NULL;
while (temp!= NULL) {
//5.如果该绝对值已经在结点上出现过,则删除该结点
if (p[abs(temp->data)] == 1) {
//临时指针指向temp->next
r->next = temp->next;
//删除temp指向的结点
free(temp);
//temp 指向删除结点下一个结点
temp = r->next;
}else
{
//6. 未出现过的结点,则将数组中对应位置置为1;
p[abs(temp->data)] = 1;
r = temp;
//继续向后遍历结点
temp = temp->next;
}
}
}
int main(int argc, const char * argv[]) {
// insert code here...
printf("线性表练习篇!\n");
Status iStatus;
LinkList La,Lb,Lc,L;
InitList(&La);
InitList(&Lb);
// ---------作业1--------
// printf("******题目1:********\n");
// //设计2个递增链表La,Lb
// for(int j = 10;j>=0;j-=2)
// {
// iStatus = ListInsert(&La, 1, j);
// }
// printf("La:\n");
// ListTraverse(La);
//
// for(int j = 11;j>0;j-=2)
// {
// iStatus = ListInsert(&Lb, 1, j);
// }
// printf("Lb:\n");
// ListTraverse(Lb);
//
// MergeList(&La, &Lb, &Lc);
// printf("Lc:\n");
// ListTraverse(Lc);
/*---------作业2--------*/
// printf("******题目2:********\n");
// ListInsert(&La, 1, 8);
// ListInsert(&La, 1, 6);
// ListInsert(&La, 1, 4);
// ListInsert(&La, 1, 2);
// printf("La:\n");
// ListTraverse(La);
//
//
// ListInsert(&Lb, 1, 10);
// ListInsert(&Lb, 1, 8);
// ListInsert(&Lb, 1, 6);
// ListInsert(&Lb, 1, 4);
// printf("Lb:\n");
// ListTraverse(Lb);
//
// Intersection(&La, &Lb, &Lc);
// printf("Lc:\n");
// ListTraverse(Lc);
/*---------作业3--------*/
// printf("******题目3:********\n");
// InitList(&L);
// for(int j = 10;j>=0;j-=2)
// {
// iStatus = ListInsert(&L, 1, j);
// }
// printf("L逆转前:\n");
// ListTraverse(L);
//
// Inverse(&L);
// printf("L逆转后:\n");
// ListTraverse(L);
//
// /*---------作业4--------*/
// printf("******题目4:********\n");
// InitList(&L);
// for(int j = 10;j>=0;j-=2)
// {
// iStatus = ListInsert(&L, 1, j);
// }
// printf("L链表:\n");
// ListTraverse(L);
//
// DeleteMinMax(&L, 4, 10);
// printf("删除链表mink与maxk之间结点的链表:\n");
// ListTraverse(L);
//
// /*---------作业5--------*/
// printf("******题目5:********\n");
// int pre[10] = {0,1,2,3,4,5,6,7,8,9};
// LeftShift(pre, 10, 3);
// for (int i=0; i < 10; i++) {
// printf("%d ",pre[i]);
// }
// printf("\n");
// /*---------作业6--------*/
printf("******题目6:********\n");
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};
int value = MainElement(A, 8);
printf("数组A 主元素为: %d\n",value);
value = MainElement(B, 8);
printf("数组B 主元素为(-1表示数组没有主元素): %d\n",value);
value = MainElement(C, 8);
printf("数组C 主元素为(-1表示数组没有主元素): %d\n",value);
// /*---------作业7--------*/
// //21,-15,15,-7,15
// printf("******题目7:********\n");
// InitList(&L);
// ListInsert(&L, 1, 21);
// ListInsert(&L, 1, -15);
// ListInsert(&L, 1, 15);
// ListInsert(&L, 1, -7);
// ListInsert(&L, 1, 15);
//
// DeleteEqualNode(&L, 21);
// ListTraverse(L);
return 0;
}