第一章 线性表
定义 :线性表是具有 相同数据类型 的n(n>=0)个数据元素的 有限序列 。
线性表的表示 :若用L命名,表示:L=(a1,a2,a3,a4,a5,……,an)
线性表的逻辑特性 :
- a1:唯一的表头元素
- an:唯一的表尾元素
- 除去a1:每个元素有且仅有一个直接前驱
- 除去an:每个元素有且仅有一个直接后继
线性表的特点 :
- 表中元素是有限个
- 表中元素具有逻辑上的顺序性,各个元素有先后次序
- 表中元素都是数据元素,每一个元素都是单个元素
- 表中元素的数据类型都相同
- 表中每个元素占用相同大小的存储空间
- 表中元素具有抽象性。线性表 仅讨论元素间的逻辑关系 ,不讨论元素的具体内容
线性表、顺序表、链表
- 线性表是 逻辑结构 ,表示一对一的相邻关系
- 顺序表是 采用顺序存储 对线性表的实现,是指存储(物理)结构
- 链表是采用 链式存储 对线性表的实现,是指存储(物理)结构
线性表的基本操作
1.1 顺序表
把线性表中所有元素按照逻辑顺序,依次存储到从指定位置开始的一块 连续存储空间 ,线性表的顺序存储叫作顺序表。
特点:
-
第一个元素的存储位置就是指定的存储位置
-
第i+1个元素的存储位置紧接在第i个元素的存储位置后面
-
逻辑相邻的两个元素物理也相邻
-
顺序表可以实现随机存取
-
存储密度高(不用额外存储指示信息)
-
逻辑相邻在物理上也相邻,插删需要移动大量元素
1.1.1 顺序表结构
线性表的顺序存储(即顺序表)的存储类型描述
一维数组静态分配
#define MaxSize 128 //定义顺序表的最大长度
typedef int ElemType; //理解成给int起个别名,以后用ElemType代表int,体现抽象数据类型
//顺序表的顺序存储结构
typedef struct {ElemType data[MaxSize]; //顺序表的主体用来存储数据元素int length; //顺序的当前长度
}SqList; //同样给顺序表struct类型起个别名,以后用SqList代表顺序表struct
静态存储的特点
- 数组的大小空间已经固定
- 空间满时,再加入新数据会导致溢出
一维数组的动态分配
#define InitSize 16 //顺序表的初始容量大小
#define MaxSize 128 //顺序表的最大容量大小typedef int ElemType;
typedef struct {ElemType* data; //动态分配数组的指针int length; //顺序表的当前长度int capacity; //记录当前容量
}DySqList;
动态存储的特点
- 在执行过程中根据需要,动态分配。
- 空间满时,可以开辟另外一块更大空间,达到扩充目的
动态分配不是链式存储,分配的空间依然是连续的,依然采用随机存取方式
动态存储的实现
与静态存储方式的实现基本一致,只是需要手动申请空间以及释放空间(这里给出代码不再细讲)
//初始化
void initList(DySqList& L) {L.data = (ElemType*)malloc(InitSize * sizeof(ElemType));if (L.data == NULL) {exit(-1); //内存分配失败}L.length = 0; //顺序表长置0L.capacity = InitSize; //更开始申请时顺序表的容量即为初始化容量大小
}
//扩容
void expansion(DySqList& L) {L.capacity += (L.capacity >> 1);if (L.capacity >= MaxSize) {L.capacity = MaxSize;exit(-1);}//先判断当前的指针是否有足够的连续空间,如果有,扩大mem_address指向的地址,并且将mem_address返回,如果空间不够,先按照newsize指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来mem_address所指内存区域L.data = (ElemType*)realloc(L.data, L.capacity * sizeof(ElemType));if (!L.data) {exit(-1);//分配内存失败}
}//创建顺序表
void createList(DySqList& L) {ElemType x;scanf("%d", &x);int i = 0;while (x != 999) {if (i >= L.capacity) {expansion(L);}//*(L.data + i) = x;L.data[i++] = x;scanf("%d", &x);}L.length = i;
}//插入操作。在表L中第pos个位置上插入指定元素e
bool insertList(DySqList& L, int pos, ElemType e) {//如果顺序表已满返回falseif (L.length >= L.capacity) {exit(-1);}//检查插入位置pos是否合法,不合法返回falseelse if (pos<1 || pos>L.length + 1) {return false;}//将最后一个元素开始直到第pos个位置处的元素依次后移for (int i = L.length - 1; i >= pos - 1; i--) {L.data[i + 1] = L.data[i];}L.data[pos - 1] = e; //将待插入的元素插入到第pos个位置上L.length++; //维持表长正确,表长+1return true;
}bool deleteList(DySqList& L, int pos, ElemType& e) {//如果顺序表为空,返回falseif (L.length == 0) {return false;}//检查pos位置的合法性else if (pos<1 || pos>L.length) {return false;}else {//将待删除元素用e接收e = L.data[pos - 1];//将第pos+1个位置处的元素直到最后一个元素依次前移for (int i = pos; i < L.length; i++) {L.data[i - 1] = L.data[i];}//维持顺序表长度的正确性L.length--;return true;}
}//按值查找操作。在表L中查找具有给定关键字值的元素,若存在返回第一个值为e的所在位置,不存在返回0
int locateElem(DySqList L, ElemType e) {int ans = 0; //用来记录最终返回的结果for (int i = 0; i < L.length; i++) {if (L.data[i] == e) {ans = i + 1; //返回所在位置,下标要+1,如果找到退出查找break;}}return ans;
}//按位查找操作。获取表L第pos个位置的元素的值
ElemType getElem(DySqList L, int pos) {//检查pos位置的合法性,不合法直接退出程序if (pos <1 || pos>L.length) {exit(0);}return L.data[pos - 1];
}//判空操作。若L为空表,则返回true,否则返回false
bool isEmpty(DySqList L) {return L.length == 0;
}//销毁顺序表
void destroyList(DySqList& L) {free(L.data); //释放内存空间L.length = 0;L.capacity = 0;L.data = NULL;
}void printList(DySqList L) {for (int i = 0; i < L.length; i++) {printf("%d ", L.data[i]);}printf("\n");
}
1.1.2 顺序表的初始化
void initList(SqList& L) {//对数据进行初始化,防止不当使用时出现脏数据for (int i = 0; i < MaxSize; i++) {L.data[i] = 0;}L.length = 0; //表长初始化为0
}
1.1.3 在指定位置插入元素
在第二个位置插入元素100
从最后一个位置到第pos个位置上的元素依次后移一位,再将100插到第pos个位置上,表长+1
//插入操作。在表L中第pos个位置上插入指定元素e
bool insertList(SqList& L, int pos, ElemType e);
算法步骤
- 检查顺序表是否已满,如果已满返回false表示插入失败
- 检查pos位置是否合法(1<=pos<=L.length+1,如果pos不合法返回false,表示插入失败
- 从最后一个位置开始直到第pos个位置上的元素依次后移
- 将待插入的元素插入到pos个位置处
- 维持表长的正确性(即将表长加一)返回true
算法实现
bool insertList(SqList& L, int pos, ElemType e) {//如果顺序表已满返回falseif (L.length >= MaxSize) {return false;}//检查插入位置pos是否合法,不合法返回falseelse if (pos<1 || pos>L.length + 1) {return false;}else {//将最后一个元素开始直到第pos个位置处的元素依次后移for (int i = L.length - 1; i >= pos - 1; i--) {L.data[i + 1] = L.data[i];}L.data[pos - 1] = e; //将待插入的元素插入到第pos个位置上L.length++; //维持表长正确,表长+1return true; }
}
复杂度分析
区别顺序表的位序(1开始)和数组下标(0开始)
1.1.4 删除指定位置的元素
删除第二个位置的元素值
从pos后一个位置到顺序表中最后一个位置依次前移,最后表长-1。此时原表中的最后一个元素还存在在表中,但是表长-1后我们可以理解顺序表中已经被删除了要删除的元素了。或者最后可以手动将最后一个元素值设为0。
//删除操作。删除表L中第pos个位置的元素,并用e返回删除元素的值
bool deleteList(SqList& L, int pos, ElemType& e);
算法步骤
- 如果表为空返回false
- 检查pos位置是否合法(1<=pos<=L.length),如果不合法返回false
- 用e接收被删除的元素值
- 将第pos+1位置上的元素直到表的最后元素依次前移
- 维持表长的正确性(表长减一)返回true
代码实现
bool deleteList(SqList& L, int pos, ElemType& e) {//如果顺序表为空,返回falseif (L.length == 0) {return false;}//检查pos位置的合法性else if (pos<1 || pos>L.length) {return false;}else {//将待删除元素用e接收e = L.data[pos - 1];//将第pos+1个位置处的元素直到最后一个元素依次前移for (int i = pos; i < L.length; i++) {L.data[i - 1] = L.data[i];}//维持顺序表长度的正确性L.length--;return true;}
}
复杂度分析
1.1.5 查找指定位置的元素(顺序查找)
//按值查找操作,在表L中查找具有给定关键字值的元素,若存在返回第一个值为e的所在位置,不存在返回0
int locateElem(SqList L, ElemType e);
算法实现
//按值查找操作,在表L中查找具有给定关键字值的元素,若存在返回第一个值为e的所在位置,不存在返回0
int locateElem(SqList L, ElemType e) {int ans = 0; //用来记录最终返回的结果for (int i = 0; i < L.length; i++) {if (L.data[i] == e) {ans = i + 1; //返回所在位置,下标要+1,如果找到退出查找break;}}return ans;
}
复杂度分析
1.1.6 查找元素所在位置
ElemType getElem(SqList L, int pos) {//检查pos位置的合法性,不合法直接退出程序if (pos <1 || pos>L.length) {exit(0);}return L.data[pos - 1];
}
时间复杂度:O(1)。顺序表支持随机存取
1.1.7 顺序表的判空
bool isEmpty(SqList L) {return L.length == 0;
}
1.1.8 销毁顺序表
void destroyList(SqList& L) {for (int i = 0; i < L.length; i++) {L.data[i] = 0;}L.length = 0;
}
静态数组的内存由编译器在栈上管理,而不需要手动释放
1.1.9 顺序表的打印
void printList(SqList L){for (int i = 0; i < L.length; i++) {printf("%d ", L.data[i]);}printf("\n");
}
1.1.10 刷题
01.从顺序表中删除具有最小值的元素( 假设唯一) 并由函数返回被删元素的值。空出的位
置由最后一个元素填补,若顺序表为空,则显示出错信息并退出运行。
遍历顺序表,找到值最小的元素下标
将最后一个元素放到最小值所在下标处。
算法思路
遍历顺序表,找到值最小的元素所在下标,将最后一个元素放到最小值所在下标处。
算法步骤
- 判断表是否为空,如果为空返回false
- 定义两个变量min及minIndex分别用来表示最小值和最小值所在位置下标,默认0位置处元素是最小值
- 从第二个元素开始遍历顺序表如果当前遍历的元素值比min小,则将当前元素赋值给min,当前下标赋值给minIndex
- 遍历结束后,minIndex位置即为最小值所在位置下标,用最后一个元素覆盖最小值
- 维持表长正确性(表长减一),返回true
算法实现
bool delMin(SqList& L) {//空表直接返回falseif (L.length == 0) {printf("表空无法操作\n");return false;}int min = L.data[0];//记录最小值int minIndex = 0; //记录最小值所在位置下标for (int i = 1; i < L.length; i++) {if (L.data[i] < min) {min = L.data[i];minIndex = i;}}L.data[minIndex] = L.data[L.length - 1]; //用最后一个位置元素覆盖被删除元素L.length--; //维持表长正确性return true;
}
02.设计一个高效算法,将顺序表L的所有元素逆置,要求算法的空间复杂度为0(1)。
算法思路
将第一个元素与最后一个进行交换,第二个与倒数第二个交换,依次类推,直到交换表长的一半次即可
算法步骤
- 记录应交换的次数(即L.length/2次)
- 进行L.length/2次元素交换即可
算法实现
//实现方法1
void reverseList(SqList& L) {int midCount = L.length / 2; //记录应交换的次数int i = 0;while (i < midCount){//首尾交换元素int temp = L.data[i];L.data[i] = L.data[L.length - 1 - i];L.data[L.length - 1 - i] = temp;i++;}
}
//实现方法2
void reverseList2(SqList& L) {int low = 0, high = L.length - 1;//当前两个"指针"分别指向第一个元素和最后一个元素//当前半部分指针和下半部分指针没有相遇时交换low指针和high指针所指向的元素while (low < high) {int temp = L.data[low];L.data[low] = L.data[high];L.data[high] = temp;low++;high--;}
}
03.对长度为n的顺序表L,编写一个时间复杂度为0(n)、空间复杂度为0(1)的算法,该算法删除线性表中所有值为x的数据元素。
算法思路
边遍历边统计边移动。遍历顺序表,统计值为x的个数count,将不是值为x的元素前移count位,维护表长正确性,将表长减count即可
算法实现
void delX(SqList& L,ElemType x) {int count = 0; //用来记录值为x的元素个数for (int i = 0; i < L.length; i++) {if (L.data[i] == x) {count++;}else {L.data[i - count] = L.data[i]; //如果当前元素值不为x,将其前移count位}}L.length -= count; //维护表长
}
04.从顺序表中删除其值在给定值s与t之间(包含s和t, 要求s < t)的所有元素,若s或t不合理或顺序表为空,则显示出错信息并退出运行。
算法思路1
与03题思路相同,只不过此时在遍历顺序表时统计的是介于s与t之间的元素个数count,将值不在s、t之间的元素前移count位,并维护表长。
算法实现1
void delS2T(SqList& L, ElemType s, ElemType t) {if (L.length == 0) {printf("顺序表为空,无法操作\n");exit(0);}else if (s >= t) {printf("s、t输入不合法\n");exit(0);}else {int count = 0; //用来统计介于s与t之间的元素个数for (int i = 0; i < L.length; i++) {if (L.data[i] >= s && L.data[i] <= t) {count++;}else {L.data[i - count] = L.data[i];}}L.length -= count;}
}
算法思路2
利用双指针,i作为遍历指针,j每次指向满足不在s、t之间要插入的位置,遍历完成后,j即为删除介于s、t之间元素后的新表长。
算法实现2
void delS2T_2(SqList& L, ElemType s, ElemType t) {if (L.length == 0) {printf("顺序表为空,无法操作\n");exit(0);}else if (s >= t) {printf("s、t输入不合法\n");exit(0);}else {int i = 0, j = 0;//i作为遍历指针,j每次指向满足不在s、t之间要插入的位置while (i < L.length) {if (L.data[i]<s || L.data[i]>t) {L.data[j++] = L.data[i]; //将需要保留的元素插入到j所指位置}i++;}L.length = j; //j即为新表长}
}
05.从有序顺序表中删除其值在给定值s与t之间(要求s<t)的所有元素,若s或t不合理或顺序表为空,则显示出错信息并退出运行.
算法思路1
找到第一个大于等于s的元素以及大于t的元素,将第一个比t大的元素直到最后一个元素前移到第一个大于等于s的位置及其后续位置。
算法实现1
void delStoT(SqList& L, ElemType s, ElemType t) {if (L.length == 0) {printf("顺序表为空,无法操作\n");exit(0);}else if (s >= t) {printf("s、t输入不合法\n");exit(0);}else {int i = 0, j;//查找第一个>=s的元素下标while (i < L.length && L.data[i] < s) {i++;}//如果不存在大于等于s的元素直接退出,没必要在删除了if (i >= L.length) {exit(0);}//找第一个>t的元素下标for (j = i; j < L.length && L.data[j] <= t; j++);//第一个比t大的元素直到最后一个元素前移到第一个大于等于s的位置及其后续位置。while (j < L.length) {L.data[i++] = L.data[j++];}L.length = i;//此时i即为新表长}
}
算法思路2
方法1中我们在进行查找第一个s和第一个大于t的元素时使用的顺序查找,时间复杂度为O(n),为此我们可以利用折半查找来降低查找的时间复杂度使之变为O(logn),当然后续移动的操作不变,由于在顺序表删除元素的时间复杂度扔为O(n),故整体时间复杂度依然为O(n),只不过对方法1的查找操作进行了代码优化。
//用二分法找第一个比s大的元素所在位置下标
int findBigEqualLeft(SqList L, ElemType target) {int left = 0, right = L.length - 1;int ans = -1;while (left <= right) {int mid = (left + right) >> 1;if (L.data[mid] >= target) {ans = mid;right = mid - 1;}else {left = mid + 1;}}return ans;
}
//用二分法找第一个比t大的元素所在位置下标
int findSmallEqualRight(SqList L, ElemType target) {int left = 0, right = L.length - 1;int ans = -1;while (left <= right) {int mid = (left + right) >> 1;if (L.data[mid] <= target) {ans = mid;left = mid + 1;}else {right = mid - 1;}}return (ans == -1 ? ans : ans + 1);
}void delStoT_3(SqList& L, ElemType s, ElemType t) {if (L.length == 0) {printf("顺序表为空,无法操作\n");exit(0);}else if (s >= t) {printf("s、t输入不合法\n");exit(0);}else {int findBigSFirst = findBigEqualLeft(L, s);if (findBigSFirst == -1) {return;}int findBigTFirst = findSmallEqualRight(L, t);if (findBigTFirst == -1) {return;}//没有比t大的元素if (findBigTFirst >= L.length) {L.length -= (findBigTFirst - findBigSFirst);return;}else {for (int i = findBigTFirst; i < L.length; i++) {L.data[findBigSFirst++] = L.data[i];}L.length = findBigSFirst;}}
}
算法思路3
利用双指针,i为工作指针,j为满足不介于s、t之间的元素所指位置,当遍历到的当前元素<s,i和j指针同时后移,当元素值介于s、t之间时,只移动工作指针i,当遍历到大于t时,将此时及后面的所有元素覆盖从i开始的元素值,最后j即为表长。
算法实现3
void delStoT_2(SqList& L, ElemType s, ElemType t) {if (L.length == 0) {printf("顺序表为空,无法操作\n");exit(0);}else if (s >= t) {printf("s、t输入不合法\n");exit(0);}else {int i = 0, j = 0; //i作为遍历指针,j指向满足要保存的元素位置for (; i < L.length; i++) {if (L.data[i] < s) {j++;}else if (L.data[i] > t) {L.data[j++] = L.data[i];}}L.length = j;}
}
06.从有序顺序表中删除所有其值重复的元素,使表中所有元素的值均不同。
算法思路1
从第二个元素依次和前一个元素比较值是否相等,如果相等记录此时出现相同元素的个数count,如果不等将当前元素前移count位,维护表长的正确性。
算法实现1
void delRepeat(SqList& L) {int count = 0; //统计重复元素的个数for (int i = 1; i < L.length; i++) {if (L.data[i] == L.data[i - 1]) {count++;}else {L.data[i - count] = L.data[i]; //将不重复的元素前移count位}}L.length -= count;
}
算法思路2
利用双指针,i和j初始都指针第二个元素,因为第一个元素自己的话一定不重复,i作为工作指针遍历顺序表,j每次指向满足不重复元素应在的位置,如果i和i-1处的值相同,只移动i指针,如果不等说明其是应保留的非重复元素将其放到j所指位置,最终j即为表长。
算法实现2
void delRepeat2(SqList &L) {int i = 1, j = 1;while (i < L.length) {if (L.data[i] != L.data[i - 1]) {L.data[j++] = L.data[i];}i++;}L.length = j;
}
07.将两个有序顺序表合并为一个新的有序顺序表,并由函数返回结果顺序表。
算法思路
同时遍历LA和LB表中的元素,比较当前两个表中的哪个元素小,谁小将其拷贝到LC中,如果相等规定先拷贝LA中的,当然也可以规定相等时先拷贝LB中的。
算法实现
bool mergeList(SqList LA, SqList LB,SqList &LC) {if (LA.length + LB.length > MaxSize) {return false;}int i = 0, j = 0, k = 0; //i,j,k分别作为LA,LB,LC的遍历指针while (i < LA.length && j < LB.length) {if (LA.data[i] <= LB.data[j]) { //谁小将其拷贝到LC中,相等默认先拷贝LA中的LC.data[k++] = LA.data[i++];}else {LC.data[k++] = LB.data[j++];}}//如果LA,LB有一个还没遍历完,将其剩下元素拷贝到LC中。两个while只会执行一个while (i < LA.length) { LC.data[k++] = LA.data[i++];}while (j < LB.length) {LC.data[k++] = LB.data[j++];}LC.length = LA.length + LB.length;return true;
}
08.已知在一维数组A[m + n]中依次存放两个线性表(a1,a2,a3,...,am)和(b1,b2,b3,...,bn).编写一
个函数,将数组中两个顺序表的位置互换,即将(b1,b2,b3,...,bn)放在(a1,a2,a3,...,am)的前面。
算法思路
先逆置前m个元素,在逆置m-m+n位置的元素,最后将整个表逆置即可。
算法实现
//将顺序表的第start到end位置上的元素进行逆置
void inverList(SqList& L, int start, int end) { int low = start - 1, high = end - 1;while (low < high) {int temp = L.data[low];L.data[low] = L.data[high];L.data[high] = temp;low++;high--;}
}
void exchangeListElem(SqList& L, int m, int n) {inverList(L, 1, m); //逆置前m个元素inverList(L,m + 1, m + n); //逆置后n个元素inverList(L, 1, m + n); //将顺序表整体进行逆置
}
09.查找值为x的元素,若找到,啧将其与后继元素位置相互交换,若找不到,则将其插入表中并使表中元素仍然递增有序。
void findExchangeInsert(SqList& L, ElemType x) {int low = 0, high = L.length - 1;int ans = -1;while (low <= high) {int mid = (low + high) / 2;if (L.data[mid] < x) {low = mid + 1;}else if (L.data[mid] > x) {high = mid - 1;}else {ans = mid;break;}}//证明已经找到了if (ans != -1 && ans != L.length - 1) {int temp = L.data[ans];L.data[ans] = L.data[ans + 1];L.data[ans + 1] = temp;}//如果没有找到if (ans == -1) {for (int i = L.length - 1; i >= low; i--) {L.data[i + 1] = L.data[i];}L.data[low] = x;L.length++;}
}
10.将顺序表元素左移p个位置,即将数据由(X0,X1....,Xn-1)变换成(Xp,Xp+1...,Xn-1,X0,X1,....,Xp-1)。
void inverseList(ElemType data[], int start, int end) {int low = start - 1;int high = end - 1;while (low < high) {int temp = data[low];data[low] = data[high];data[high] = temp;low++;high--;}
}void leftRemove(ElemType data[], int p, int size) {if (p > size || p < 1) {return;}inverseList(data, 1, p);inverseList(data, p + 1, size);inverseList(data, 1, size);
}
11.求两个序列的中位数,例如,S1={11,13,15,17,19},S2 = {2,4,6,8,20},则S1和S2的中位数为11。
//思路1,利用归并排序
int findMid1(SqList LA, SqList LB) {SqList LC;initList(LC);if (LA.length + LB.length >= MAXSIZE) {exit(-1);}int i = 0, j = 0, k = 0;while (i < LA.length && j < LB.length) {if (LA.data[i] <= LB.data[j]) {LC.data[k++] = LA.data[i++];}else {LC.data[k++] = LB.data[j++];}}//当前LA还没有遍历完while (i < LA.length) {LC.data[k++] = LA.data[i++];}//当前LB还没有遍历完while (j < LB.length) {LC.data[k++] = LB.data[j++];}LC.length = LA.length + LB.length;return LC.data[LA.length - 1];
}
//思路2,同样利用归并排序思路,但不使用额外的空间
int findMid(SqList L1, SqList L2) {int i = 0, j = 0;int count = 1;int size = L1.length;while (i < L1.length && j < L2.length && count < size) {if (L1.data[i] < L2.data[j]) {i++;}else {j++;}count++;}return L1.data[i] < L2.data[j] ? L1.data[i] : L2.data[j];
}
//思路3,利用二分查找的思路
int findMid2(SqList L1, SqList L2) {int s1 = 0, s2 = 0, d1 = L1.length - 1, d2 = L2.length - 1;while (s1 != d1 || s2 != d2) {int m1 = (s1 + d1) / 2;int m2 = (s2 + d2) / 2;if (L1.data[m1] == L2.data[m2]) {return L1.data[m1];}if (L1.data[m1] < L2.data[m2]) {//证明是奇数个if ((s1 + d1) % 2 == 0) {s1 = m1;d2 = m2;}else {s1 = m1 + 1;d2 = m2;}}else {if ((s2 + d2) % 2 == 0) {s2 = m2;d1 = m1;}else {s2 = m2 + 1;d1 = m1;}}}return L1.data[s1] < L2.data[s2] ? L1.data[s1] : L2.data[s2];
}
12.顺序表L中的数据元素递增有序。试写一个算法,将x插入到顺序表适当的位置上,以保持该表的有序性。
/*思路1.利用顺序查找1.找到第一个大于等于x元素所在位置i2.将最后一个元素开始一直到第i个元素后移3.将元素e插入到i位置上
*/
void insertXtoList1(SqList& L, ElemType x) {if (L.length >= MAXSIZE) {return;}int i = 0;while (i < L.length && L.data[i] < x) {i++;}for (int j = L.length - 1; j >= i; j--) {L.data[j + 1] = L.data[j];}L.data[i] = x;L.length++;
}
/*思路2利用二分查找1.用二分查找找到第一个大于x元素所在位置2.将最后一个元素依次到第一个大于x的元素后移3.空出的位置插入元素x
*/void insertXtoList2(SqList& L, ElemType x) {if (L.length >= MAXSIZE) {return;}int low = 0, high = L.length - 1;while (low <= high) {int mid = (low + high) / 2;if (L.data[mid] <= x) {low = mid + 1;}else {high = mid - 1;}}for (int i = L.length - 1; i >= low; i--) {L.data[i + 1] = L.data[i];}L.data[low] = x;L.length++;
}
13. 已知顺序表中每个元素为不相等的整数。设计算法把所有的奇数移到所有的偶数前面(要求时间最少,辅助空间最少)
void partitonList(SqList& L) {int i = 0, j = L.length - 1;while (i < j) {while (i < j&& L.data[i] % 2 == 1) {i++;}while (i<j && L.data[j] % 2 == 0) {j--;}int temp = L.data[i];L.data[i] = L.data[j];L.data[j] = temp;i++;j--;}
}
14.请写一个算法,在顺序表中查找指定的数据,查找成功后将该记录放到顺序表的最前面,而把其他记录后退一个位置
void searchValue(SqList& L, ElemType x) {int pos = 0;for (int i = 0; i < L.length; i++) {if (x == L.data[i]) {pos = i;break;}}for (int k = pos - 1; k >= 0; k--) {L.data[k + 1] = L.data[k];}L.data[0] = x;
}
15.给你一个顺序表L ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
int singleNumber(SqList L) {int ans = 0;for (int i = 0; i < L.length; i++) {ans ^= L.data[i];}return ans;
}
16.给你一个顺序表L,只有可以将其划分为三个和相等的 非空 部分时才返回 true
,否则返回 false
。
示例 1:
输入:L = [0,2,1,-6,6,-7,9,1,2,0,1] 输出:true 解释:0 + 2 + 1 = -6 + 6 - 7 + 9 + 1 = 2 + 0 + 1
示例 2:
输入:L = [0,2,1,-6,6,7,9,-1,2,0,1] 输出:false
示例 3:
输入:L = [3,3,6,5,-2,2,5,1,-9,4] 输出:true 解释:3 + 3 = 6 = 5 - 2 + 2 + 5 + 1 - 9 + 4
bool canThreePartsEqualSum(SqList L) {int sum = 0;for (int i = 0; i < L.length; i++) {sum += L.data[i];}if (sum % 3 != 0) {return false;}int partSum = sum / 3;int part[2] = { 0 };int low = 0, high = L.length - 1;while (low < L.length-2) {part[0] += L.data[low];if (part[0] == partSum) {break;}low++;}if (low >= L.length - 2) {return false;}while (high - low >= 2) {part[1] += L.data[high];if (part[1] == partSum) {break;}high--;}if (part[1] == partSum) {return true;}else {return false;}
}
17.给定一个顺序表L和一个整数目标值target,请你设计一个时间复杂度尽可能高效的算法,在该顺序表中找出是否存在有两个元素之和等于target,若存在返回true否则返回false。假设顺序表L中的元素值均在(0-1000之间).
//思路1暴力求解时间复杂度O(n²)
bool twoSum(SqList L, int target) {for (int i = 0; i < L.length; i++) {for (int j = 0; j < L.length; j++) {if (i != j && L.data[i] + L.data[j] == target) {return true;}}}return false;
}
//思路2用空间换时间,时间复杂度O(n)bool twoSum2(SqList L, int target) {int arr[1001] = {0};for (int i = 0; i < L.length; i++) {arr[L.data[i]] = 1;}for (int i = 0; i < L.length; i++) {int ans = target - L.data[i];if (arr[ans] == 1) {return true;}}return false;
}
18.给你一个顺序表L,请你选择L的两个不同下标 i 和 j,使 (L.data[i])*(L.data[j])取得最大值。请你计算并返回该式的最大值。
int max(int a, int b) {return a >= b ? a : b;
}int maxProduct(SqList L) {if (L.length < 2) {return 0;}int firstmax = max(L.data[0], L.data[1]);int secondmax = (firstmax == L.data[0]) ? L.data[1] : L.data[0];for (int i = 2; i < L.length; i++) {if (L.data[i] > firstmax) {secondmax = firstmax;firstmax = L.data[i];}else if (L.data[i] > secondmax) {secondmax = L.data[i];}}return firstmax * secondmax;
}
19. 给你一个目标值target,你需要编写算法使得目标值等于target的放在中间,目标值小于target的放在左侧,目标值大于target的放在右侧。
void dividList(SqList& L, int target){int smallPart = -1;int bigPart = L.length;int i = 0;while (i < bigPart) {if (L.data[i] < target) {swap(L, ++smallPart, i++);}else if (L.data[i] > target) {swap(L, --bigPart, i);}else {i++;}}
}
1.2单链表
1.2.1定义
通过一组 任意的存储单元 来存储线性表中的数据元素,是线性表的 链式存储。
单链表节点类型描述
- 因为逻辑相邻不一定物理相邻,所以需要额外的指针来存储后继信息。
- 单链表节点的组成
- data:数据域,存放数据元素
- next:指针域,存放后继节点的地址
- 代码定义
typedef struct {ElemType data; //数据域LNode* next; //指针域,指向后继结点
}LNode,*LinkedList;
typedef struct {ElemType data;LNode* next;
}LNode;typedef struct {LNode* head; //头指针int length; //记录表长
}LinkedList;
单链表特点
- 解决了顺序表插删需要大量移动元素的缺点
- 引入了额外的指针域,浪费了空间
- 单链表是非随机存取的存储结构,查找需要从头遍历
“头指针”与“头结点”
``
1.2.2头插法创建单链表(不带头结点)
void createListByHead(LinkedList& L) {ElemType x;scanf("%d", &x);while (x != 999) {LNode* node = (LNode*)malloc(sizeof(LNode)); //申请结点内存空间node->data = x; //为新结点赋值if (L == NULL) {node->next = NULL; //如果是插入的第一个结点。用头插法的话最后他是尾结点将其next指空}else {node->next = L; //如果不是插入的第一个结点,将当前结点直接之前的头指针}L = node; //更新最新的结点作为头指针scanf("%d", &x);}
}
1.2.3尾插法创建单链表(不带头结点)
void createListByTail(LinkedList& L) {LNode* tail = NULL; //尾指针ElemType x;scanf("%d", &x);while (x != 999) {LNode* node = (LNode*)malloc(sizeof(LNode));node->data = x;node->next = NULL;if (L == NULL) { //如果是第一个结点,头尾都指向该结点L = node;tail = node;}else {tail->next = node; //如果不是第一个结点,尾指针的next指向该结点tail = node; //当前新添加结点作为尾结点}scanf("%d", &x);}
}
1.2.4返回第pos个位置上的结点(不带头结点)
LNode* getNode(LinkedList L, int pos) {//位置不对返回NULLif (pos < 0) {return NULL;}int i = 1;LNode* p = L;while (p != NULL && i < pos) {p = p->next;i++;}return p;
}
1.2.5按值查找结点(不带头结点)
LNode* locateElem(LinkedList L, ElemType e)
{LNode* p = L;while (p!= NULL&&p->data!=e) {p = p->next;}return p;
}
1.2.6在第pos个位置插入元素e(不带头结点)
void insertElem(LinkedList& L, int pos, ElemType e) {//检查pos位置的合法性if (pos < 1 || pos>length(L) + 1) {return;}1//创建新结点LNode* node = (LNode*)malloc(sizeof(LNode));node->data = e;//如果是在第一个位置插入元素,新结点作为头指针if (pos == 1) {node->next = L;L = node;}else {LNode *pre = getNode(L, pos - 1); //找到插入位置的前一个结点node->next = pre->next; pre->next = node;}
}
1.2.7删除第pos个位置的元素结点,并用e返回被删除结点的元素值(不带头结点)
void deleteElem(LinkedList& L, int pos, ElemType& e) {//检查pos位置的合法性if (pos < 1 || pos>length(L)) {return;}//查找被删除结点LNode *removed = getNode(L, pos);//如果要删除第一个结点元素,直接将要删除的下一个元素作为头指针if (pos == 1) {L = removed->next;}else {LNode* pre = getNode(L, pos - 1); //找到被删除元素结点的前一个pre->next = removed->next; }e = removed->data; //将要删除结点的值赋值给e接收free(removed); //释放被删除元素的空间内存
}
1.2.8求表长(不带头结点)
int length(LinkedList L) {LNode* p = L;if (p == NULL) {return 0;}int ans = 0;while (p != NULL) {p = p->next;ans++;}return ans;
}
1.2.9单链表的基本操作(带头结点)
typedef int ElemType;typedef struct LNode {ElemType data;LNode* next;
}LNode, * LinkedList;
//初始化单链表
void initLinkedList(LinkedList& L) {L = (LNode*)malloc(sizeof(LNode)); //申请头结点if (L) {L->next = NULL;L->data = 0; //头结点的数据域保存链表长度}
}
//头插法创建单链表
void createLinkedListWithHead(LinkedList& L) {ElemType x;scanf("%d", &x);while (x != 999) {LNode* cur = (LNode*)malloc(sizeof(LNode)); //申请新结点空间cur->data = x; cur->next = L->next; //新结点的next指向头结点的nextL->next = cur; //头结点的next指向当前新结点L->data += 1; //链表长度+1scanf("%d", &x);}
}
//尾插法创建单链表
void createLinkedListWithTail(LinkedList& L) {ElemType x;LNode* tail = L;scanf("%d", &x);while (x != 999) {LNode* cur = (LNode*)malloc(sizeof(LNode));cur->data = x; cur->next = NULL;tail->next = cur; //尾指针的next每次指向最新结点tail = cur; //最新结点成为尾指针L->data += 1; //表长+1scanf("%d", &x);}}
//返回第pos个位置上的结点
LNode* getNode(LinkedList L, int pos) {//检查pos是否合法,data是链表的长度if (pos<0 || pos>L->data) {return NULL;}int i = 0; LNode* p = L;while (p && i < pos){p = p->next;i++;}return p;
}
//在第pos个位置插入元素e
void insertElem(LinkedList& L, int pos, ElemType e){if (pos<1 || pos>L->data + 1) {return;}LNode* cur = (LNode*)malloc(sizeof(LNode));cur->data = e;LNode * pre = getNode(L,pos - 1); //获得要插入位置的前一个位置结点cur->next = pre->next; //新结点的next指向前一个结点的nextpre->next = cur; //前一个结点的next指向当前结点L->data += 1; //表长+1
}
//删除第pos个位置的元素结点,并用e返回被删除结点的元素值
void deleteElem(LinkedList& L, int pos, ElemType& e){//检查pos是否合法if (pos<1 || pos>L->data) {return;}LNode* removed = getNode(L, pos); //记录要删除元素LNode* pre = getNode(L, pos - 1); //记录要删除元素的前一个元素结点e = removed->data; //被删除元素用e接收返回pre->next = removed->next; //被删除的前一个结点指向其后一个结点L->data--; //表长-1free(removed); //释放删除结点的空间
}
//打印链表
void printLinkedList(LinkedList L) {LNode* p = L->next; //第一个数据元素是头结点的后继结点元素while (p) {printf("%d->", p->data);p = p->next;}printf("\n");
}
1.2.10刷题
01.设计一个递归算法,删除不带头结点的单链表L中所有值为x的结点。
//递归
void delX(LinkedList& L,ElemType x) {if (L == NULL) { //终止条件return;}if (L->data == x) { //如果是被删除元素结点LNode* p = L; //防止断链L = L->next; //记录被删除元素的下一个结点,free(p); //释放被删除元素空间delX(L, x); //递归下一个结点}else { //如果当前元素的值不是被删除元素,递归下一个元素delX(L->next, x);}
}
//非递归
void delX2(LinkedList& L, ElemType x) {if (L == NULL) {return;}LNode* removed;//用来记录被删除元素结点while (L!=NULL&&L->data == x) { //如果链表还没有遍历完找到第一个值不为x结点,同时将遍历过程中值为x的结点都删除removed = L;//如果当前结点值为x记录当前结点为待删除结点L = L->next; //L后移free(removed); //释放被删除结点的空间}if (L == NULL) {//如果L已经为NULL,证明结点值都为x且已被删除直接返回return;}LNode* p = L->next; //此时L一定头指针,节点元素值不为xLNode* pre = L; //用来指向不是值为x的元素的最后一个结点while (p){//如果当前节点的值为x,removed记录待删除节点,p后移,pre指向待删除结点的后继节点,释放removed空间if (p->data == x) {removed = p;p = p->next;pre->next = removed->next;free(removed);}//如果当前节点的值不为x,pre和p同时后移else {pre = pre->next;p = p->next;}}pre->next = NULL; //删除结束后pre指向最后一个结点,将其next指空
}
02.在带头结点的单链表L中,删除所有值为x的结点,并释放其空间,假设值为x的结点
不唯一,试编写算法以实现.上述操作。
void delX2(LinkedList& L, ElemType x) { LNode* p = L->next;//p作为遍历指针从首结点开始if (p == NULL) {//如果链表为空,返回return;}LNode* removed;//用来记录被删除元素结点LNode* pre = L;//pre始终指向最后一个值不为x的结点while (p) {//如果当前节点的值为x,removed记录待删除节点,p后移,pre指向待删除结点的后继节点,释放removed空间if (p->data == x) {removed = p;p = p->next;pre->next = removed->next;free(removed);}//如果当前节点的值不为x,pre和p同时后移else {pre = pre->next;p = p->next;}}pre->next = NULL;//删除结束后pre指向最后一个结点,将其next指空
}
03.设L为带头结点的单链表,编写算法实现从尾到头反向输出每个结点的值。
//递归
void inverPrintList(LinkedList L) {LNode* p = L->next; //从第一个首节点开始if (p == NULL) { //终止条件return;}inverPrintList(p); //递归printf("%d->", p->data);
}
//非递归
void inverPrintList2(LinkedList L) {//申请动态数组用来保存链表中的元素值ElemType* set = (ElemType*)malloc(sizeof(ElemType) * L->data);int i = 0;LNode* p = L->next;while (p) {set[i++] = p->data;p = p->next;}for (int j = i - 1; j >= 0; j--) {printf("%d->", set[j]);}printf("\n");
}
04.试编写在带头结点的单链表L中删除一个最小值结点的高效算法(假设最小值结点是唯一的)。
void delMin(LinkedList& L) {if (L->next == NULL) {return;}LNode* p = L->next; //从首节点开始LNode* pre = L; //始终是p的前一个位置的指针LNode* min = p; //记录最小值指针,假设初始第一个元素就是最小值LNode* minpre = L; //记录最小值的前一个元素指针while (p) {if (p->data < min->data) { //如果当前元素的值比最小值小min = p; //最小值指针指向当前结点minpre = pre; //最小值的前一个指向pre}//每次p和pre都后移p = p->next;pre = pre->next;}minpre->next = min->next; //删除min结点free(min); //释放空间
}
05.试编写算法将带头结点的单链表就地逆置,所谓“就地”是指辅助空间复杂度为0(1)。
void reverse(LinkedList& L) {LNode* cur = L->next;LNode* next = NULL;L->next = NULL;while (cur) {next = cur->next;cur->next = L->next;L->next = cur;cur = next;}
}
06.有一个带头结点的单链表L,设计一个算法使其元素递增有序。
void sortListAsc(LinkedList& L) {//如果链表为空,或者链表只有一个结点直接返回不需要排序if (L->next == NULL || L->next->next == NULL) {return;}//从第二个结点开始LNode* p = L->next->next;L->next->next = NULL;while (p != NULL) {LNode* next = p->next; //记录一下p的后继防止断链LNode* head = L->next; //head作为每次已排好序的部分链表中的首节点同时作为遍历指针LNode* pre = L; //遍历指针head的前一个结点while (head != NULL && p->data > head->data) {//如果不满足插入位置pre和head后移pre = pre->next; head = head->next;}p->next = head; //在head和pre之间插入ppre->next = p;p = next; //p后移重新遍历未进行排序的结点操作}
}
07.设在一个带表头结点的单链表中所有元素结点的数据值无序,试编写一个函数,删除表中所有介于给定的两个值(作为函数参数给出)之间的元素的元素(若存在)。
void delSToT(LinkedList& L, ElemType s, ElemType t){if (L->next == NULL) {return;}LNode* p = L->next; //遍历指针从首节点开始LNode* pre = L; //始终指向不是删除元素的最后位置while (p) {if (p->data >= s && p->data <= t) { //如果是被删除元素LNode* removed = p; //记录被删除元素p = p->next; //后移pre->next = p; //最后一个非删除元素指向当前删除元素的后继free(removed); //释放删除结点空间}else { //如果不在删除范围,p和pre后移p = p->next;pre = pre->next;}}
}
08.给定两个单链表,编写算法找出两个链表的公共结点。
LNode* findCommon(LinkedList L1, LinkedList L2){int len1 = length(L1);int len2 = length(L2);LNode* longhead = len1 > len2 ? L1 : L2;LNode* shorthead = longhead == L1 ? L2 : L1;//int diff = abs(len1 - len2);int diff = len1 - len2 > 0 ? len1 - len2 : len2 - len1;while (diff > 0) {longhead = longhead->next;diff--;}while (longhead) {if (longhead == shorthead) {return longhead;}longhead = longhead->next;shorthead = shorthead->next;}return NULL;
}
09.给定一个带表头结点的单链表,设head为头指针,结点结构为(data, next),data为整型元素,next为指针,试写出算法:按递增次序输出单链表中各结点的数据元素,并释放结点所占的存储空间(要求 : 不允许使用数组作为辅助空间)。
LNode* delMin2(LinkedList& L) {if (L->next == NULL) {return L;}LNode* p = L->next; //从首节点开始LNode* pre = L; //始终是p的前一个位置的指针LNode* min = p; //记录最小值指针LNode* minpre = L; //记录最小值的前一个元素指针while (p) {if (p->data < min->data) {min = p;minpre = pre;}p = p->next;pre = pre->next;}minpre->next = min->next;return min;
}void sortListAndDelete(LinkedList& L) {if (L->next == NULL) {return;}LNode *min = delMin2(L);printf("%d->", min->data);free(min);if (L->next != NULL) {sortListAndDelete(L);}
}
10. 将一个带头结点的单链表A分解为两个带头结点的单链表A和B,使得A表中含有原表中序号为奇数的元素, 而B表中含有原表中序号为偶数的元素,且保持其相对顺序不变。
LinkedList resolveList(LinkedList& LA) {LNode* p = LA->next;if (p == NULL) {return NULL;}LinkedList LB = (LNode*)malloc(sizeof(LNode)); //申请LB的头结点LNode* tailA = LA; //LA的尾指针LNode* tailB = LB; //LB的尾指针int i = 1; //用来标记遍历的结点的序号是奇序号还是偶序号while (p) {//奇数时if (i % 2 == 1) {tailA->next = p;tailA = p;}//偶数时else {tailB->next = p;tailB = p;}p = p->next;i++;} tailA->next = NULL; //最后不要忘了把LA和LB的尾指针的next指向空tailB->next = NULL;return LB;
}
11.设C={a1,b1,a2, b2,...,an bn}为线性表,采用带头结点的单链表存放,设计一个就地算
法,将其拆分为两个线性表,使得A= {a1, a2,...,an}, B= {bn,..., b2, b1}.
void resolveList2(LinkedList LC, LinkedList& LA, LinkedList& LB) {if (LC->next == NULL) {return;}LA =(LNode*) malloc(sizeof(LNode)); //LA的头结点LB =(LNode*) malloc(sizeof(LNode)); //LB的头结点LA->next = NULL;LB->next = NULL;LNode* p = LC->next;LNode* tail = LA;int i = 1;while (p) {LNode* next = p->next; //记录p的后继防止发生断链if (i % 2 == 1) { //当序号为奇数的时候,尾插法插到LA中tail->next = p;tail = p;}else { //当序号为偶数时,用头插法插到LB中p->next = LB->next;LB->next = p;}i++;p = next;}tail->next = NULL;
}
12.在一个递增有序的线性表中,有数值相同的元素存在。若存储方式为单链表,设计算法去掉数值相同的元素,使表中不再有重复的元素, 例如(7, 10, 10, 21, 30, 42, 42, 42, 51, 70)将变为(7, 10, 21, 30, 42, 51, 70)。
//递归
void delSame(LinkedList& L) {//递归终止条件if (L == NULL||L->next == NULL) {return;}//从第二个节点开始与前一个结点元素值比较,pre为前一个cur为当前遍历结点LNode* pre = L; LNode* cur = L->next;//如果当前结点值等于前一个删除当前节点并释放空间if (pre->data == cur->data) {pre->next = cur->next;free(cur);delSame(L);}else {delSame(L->next);}
}
//非递归
void delSame2(LinkedList& L) {if (L == NULL || L->next == NULL) {return;}LNode* pre = L;LNode* cur = L->next;while (cur){if (cur->data == pre->data) {LNode* removed = cur;pre->next = cur->next;cur = cur->next;free(removed);}else {pre = pre->next;cur = cur->next;}}
}
13.假设有两个按元素值递增次序排列的线性表,均以单链表形式存储。请编写算法将这两
个单链表归并为一个按元素值递减次序排列的单链表,并要求利用原来两个单链表的结
点存放归并后的单链表。
LinkedList inverseMergeList(LinkedList LA, LinkedList LB) {if (LA == NULL && LB == NULL) {return NULL;}//给新链表创建个头结点LinkedList L = (LNode*)malloc(sizeof(LNode));L->next = NULL;LNode* p = LA; //p是LA的遍历指针LNode* q = LB; //q是LB的遍历指针while (p && q) {LNode* pnext = p->next; //记录p的后继防止断链LNode* qnext = q->next;//记录q的后继防止断链//谁小谁先用头插法插到新链表中if (p->data <= q->data) {p->next = L->next;L->next = p;p = pnext;}else {q->next = L->next;L->next = q;q = qnext;}}//下面两个while只会执行一个,谁还没遍历完再将剩余的插到新链表中while (p) {LNode* pnext = p->next;p->next = L->next;L->next = p;p = pnext;}while (q) {LNode* qnext = q->next;q->next = L->next;L->next = q;q = qnext;}//我们建立一个头结点是为了方便操作,用完释放掉LNode* head = L->next;free(L);return head;
}
14.设A和B是两个单链表(带头结点),其中元素递增有序。设计一个算法从A和B中的
公共元素产生单链表C,要求不破坏A、B的结点。
LinkedList findCommonElem(LinkedList LA, LinkedList LB) {LinkedList L = (LNode*)malloc(sizeof(LNode)); //申请头结点L->next = NULL; LNode* tail = L; //尾指针LNode* p = LA->next; //LA和LB的遍历指针LNode* q = LB->next;while (p && q) {if (p->data == q->data) { //值相等创建新结点执行尾插法LNode* node = (LNode*)malloc(sizeof(LNode));node->data = p->data;tail->next = node;tail = node;p = p->next;q = q->next;}else if (p->data < q->data) { //谁小谁后移p = p->next;}else {q = q->next;}} tail->next = NULL; //最后将尾指针指空return L;
}
15.已知两个链表A和B分别表示两个集合,其元素递增排列。编制函数,求A与B的交集,并存放于A链表中。
void unionList(LinkedList& LA, LinkedList LB) {LNode* head = (LNode *)malloc(sizeof(LNode)); //头结点head->next = NULL;LNode* tail = head; //尾指针LNode* p = LA; //p和q分别为LA和LB的遍历指针LNode* q = LB;while (p && q) {if (p->data == q->data) {tail->next = p;tail = p;p = p->next;q = q->next;}else if (p->data < q->data) {p = p->next;}else {q = q->next;}}tail->next = NULL;LA = head->next;free(head);
}
16.两个整数序列A=a1,a2, a3,...,am和B=b1,b2,b3,....,bn已经存入两个单链表中,设计一
个算法,判断序列B是否是序列A的连续子序列。
bool isChildList(LinkedList LA, LinkedList LB) {LNode* p = LA; //LA的遍历指针LNode* q = LB; //LB的遍历指针LNode* start = p; //每次记录p开始的结点位置while (p && q) {if (p->data == q->data) { //相等一块后移p = p->next;q = q->next;}else {start = start->next; //不相等start后移p = start; //p重新来到start的位置q = LB; //q重头开始遍历}}if(q==NULL){return true;}else {return false;}
}
//思路2
bool isSubsequence(LinkedList LA, LinkedList LB) {LNode* p = LA; //p和q分别作为LA和LB的遍历指针LNode* q = LB; while (p && q) {LNode* pnext = p->next;while(p &&q &&p->data == q->data) {p = p->next;q = q->next;}if (q == NULL) {return true;}else if (p != NULL) {p = pnext;q = LB;}}return false;
}
17. 判断循环双链表是否对称
bool isSymmetry(DLinkedList L) {DNode* p = L->next; //p刚开始在首节点处DNode* q = L->prior; //q来到最后一个位置处while (p != q && q->next != p) {if (p->data == q->data) {p = p->next;q = q->prior;}else {return false;}}return true;
}
18.有两个循环单链表,链表头指针分别为h1和h2, .编写一个函数将链表h2链接到链表
h1之后,要求链接后的链表仍保持循环链表形式。
LinkedList connectList(LinkedList& h1, LinkedList& h2) {LNode* p = h1; //他俩都是为了找到最后一个节点LNode* q = h2;while (p->next != h1) {p = p->next;}while (q->next != h2) {q = q->next;}p->next = h2;q->next = h1;return h1;
}
19.设头指针为L的带有表头结点的非循环双向链表,其每个结点中除有pre(前驱指针)、
data ( 数据)和next (后继指针)域外,还有一个访问频度域freq.在链表被启用
前,其值均初始化为零。每当在链表中进行一次Locate(L,X)运算时,令元素值为x
的结点中freq 域的值增1,并使此链表中结点保持按访问频度非增(递减)的顺序排
列,同时最近访问的结点排在频度相同的结点前面,以便使频繁访问的结点总是靠近表
头.试编写符合.上述要求的Locate (L,x)运算的算法,该运算为函数过程,返回找到
结点的地址,类型为指针型。
DNode* Locate(DLinkedList& L, ElemType x) {DNode* p = L->next;while (p && p->data != x) {p = p->next;}if (p == NULL) {return NULL;}else {p->freq++;if (p->prior == L || p->freq < p->prior->freq) {return p;}if (p->next != NULL) {p->next->prior = p->prior;}p->prior->next = p->next;DNode* pre = p->prior;//从p的前面结点开始和p的freq比较while (pre !=L && p->freq >= pre->freq) {pre = pre->prior;}p->next = pre->next;pre->next->prior = p;p->prior = pre;pre->next = p;return p;}
}
20.判断链表是否有环
//判断单链表是否有环
bool hasCircle(LinkedList L) {LNode* s = L, * q = L; //s和q分别是快慢指针while (q != NULL && q->next != NULL) {s = s->next;q = q->next->next;if (s == q) {return true;}}return false;
}
21.给你单链表的头指针L和两个整数 left和 right,其中 left <= right 。请你反转从位置 left 到位置 right的链表节点,返回反转后的链表(其中1<=left<=right<=链表长度)。
void reverseListByAssignPosition(LinkedList& L, int left, int right) {if (L == NULL || left > right) {return;}LNode* dummyHead = (LNode *)malloc(sizeof(LNode));dummyHead->next = L;int i = 1;LNode* pre = dummyHead;LNode* p = L;while (i < left) {p = p->next;pre = pre->next;i++;}LNode* front = NULL;LNode* cur = p;for (int j = i; j <= right; j++) {LNode* next = p->next;p->next = front;front = p;p = next;}pre->next = front;cur->next = p;L = dummyHead->next;
}
22.两个整数序列A=a1, a2, a3,am和B=b1, b2, b3, bn,已经存入两个单链表中,设计一个算法,判断序列B是否是序列A的连续子序列。
bool isSubsequence(LinkedList LA, LinkedList LB) {LNode* p = LA; //p和q分别作为LA和LB的遍历指针LNode* q = LB;while (p && q) {LNode* pnext = p->next;while (p && q && p->data == q->data) {p = p->next;q = q->next;}if (q == NULL) {return true;}else if (p != NULL) {p = pnext;q = LB;}}return false;
}
23.设将n(n>1)个整数存放到不带头结点的单链表L中,设计算法将L中保存的序列循环
右移k (0<k<n)个位置。例如,若k=1,则将链表{0,1,2, 3}变为{3,0,1,2}。要求:
1)给出算法的基本设计思想。
2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。
3)说明你所设计算法的时间复杂度和空间复杂度。
void rightRemoveKposition(LinkedList &L,int k) {LNode* p = L;LNode* q = L;int step = k;//记录步数,让p先走step步while (k > 0) {p = p->next;k--;}while (p->next) {p = p->next;q = q->next;}p->next = L;L = q->next;q->next = NULL
}
//思路2
void rightRemoveKposition2(LinkedList& L, int k) {LNode* p = L;int len = 1;while (p->next != NULL) {p = p->next;len++;}p->next = L;p = L;int i = 1;while (i < len - k) {p = p->next;i++;}L = p->next;p->next = NULL;
}
24.设有一个长度n(n为偶数)的不带头结点的单链表,且结点值都大于0,设计算法求这
个单链表的最大孪生和。孪生和定义为一个结点值与其孪生结点值之和,对于第i个结
点(从0开始),其孪生结点为第n-i-1个结点。要求:
1)给出算法的基本设计思想。
2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。
3)说明你的算法的时间复杂度和空间复杂度。
int getmax(int a, int b) {return a >= b ? a : b;
}int getBigSum(LinkedList L) {LNode* s = L, * q = L;while (q) {s = s->next;q = q->next->next;}LNode* pre = NULL;while (s) {LNode* next = s->next;s->next = pre;pre = s;s = next;}int max = 0;s = L;while (pre) {max = getmax(max, pre->data + s->data);pre = pre->next;s = s->next;}return max;
}
25.将单链表L进行划分,且保持其相对次序 。例如L = {1,5,4,4,2,5,3,2,6,5,3,4}.且target=4,最终LA{1,2,3,2,3,4,4,4,5,5,6,5}
void resolveList2(LinkedList &L,int target) {LNode* p = L;//申请LA,LB,LC的头结点为了方便操作的统一LNode* headA = (LNode*)(malloc(sizeof(LNode)));LNode* headB = (LNode*)(malloc(sizeof(LNode)));LNode* headC = (LNode*)(malloc(sizeof(LNode)));//LA,LB,LC的尾指针LNode* tailA = headA;LNode* tailB = headB;LNode* tailC = headC;while (p) {if (p->data < target) {tailA->next = p;tailA = p;}else if (p->data == target) {tailB->next = p;tailB = p;}else {tailC->next = p;tailC = p;}p = p->next;}tailA->next = headB->next;tailB->next = headC->next;tailC->next = NULL;L = headA->next;free(headA);free(headB);free(headC);
}
26.设计算法删除单链表上值一样的多余结点的算法
void delSame3(LinkedList& L) {LNode* p = L;LNode* tail = L; //tail作为已经不重复元素的尾指针LNode* q = L->next; //从第二个元素开始依次和前面不重复的元素比看是否会有重复L->next = NULL;while (q != NULL) {p = L;LNode* next = q->next; //记录后继防止断链while (p) {if (q->data == p->data) {free(q);break;}p = p->next;}//说明在已经不重复的链表中没有找到与q重复的元素,将q连到不重复链表去if (p == NULL) {tail->next = q;tail = q;tail->next = NULL;}q = next;}
}
27.已知两个链表A和B分别表示两个集合。编制函数,求A与B的交集,并存放于C链表中,假设集合A与集合B中无重复元素
void unionList(LinkedList LA, LinkedList LB, LinkedList& LC) {LNode* p = LA;LNode* q = LB;LC = NULL;LNode* tail = NULL;while (p != NULL) {q = LB;LNode* next = p->next;while (q != NULL) {if (q->data == p->data) {if (LC == NULL) {LC = p;tail = p;}else {tail->next = p;tail = p;tail->next = NULL;}}q = q->next;}p = next;}
}
28.[2015统考真题]用带头结点单链表保存m个整数,结点的结构为[data][next], 且 |data|≤
n(n为正整数)。现要求设计一个时间复杂度尽可能高效的算法,对于链表中data的绝对值相等的结点,
仅保留第一次出现的结点而删除其余绝对值相等的结点。例如,若L={21,-15,-15,-7,15}的头结点单链表
删除完之后变为L={21,-15,-7};
int fabs(int a) {return a >= 0 ? a : -a;
}
//以空间换时间(时间复杂度O(m),空间复杂度O(1))
void delFabSameNum(LinkedList &L,int n) {int* arr = (ElemType*)malloc(sizeof(ElemType) * (n + 1));LNode* p = L->next;LNode* tail = L;while (p) {int ans = fabs(p->data);LNode* next = p->next;if (arr[ans] == 1) {free(p);}else {tail->next = p;tail = p;tail->next = NULL;arr[ans] = 1;}p = next;}
}//思路2,暴力法,时间复杂度O(m²),空间复杂度O(1)
void delFabSameNum(LinkedList& L) {if (L->next == NULL || L->next->next == NULL) {return;}LNode* p = L->next->next;LNode* tail = L->next;LNode* q = L->next;tail->next = NULL;while (p) {LNode* next = p->next;q = L->next;while (q) {if (fabs(q->data) == fabs(p->data)) {free(p);p = next;break;}else {q = q->next;}}if (q == NULL) {tail->next = p;tail = p;tail->next = NULL;p = next;}}
}
29.线性表(a1,a2,a3, ...an)中元素递增有序且按顺序存储于计算机内。要求设计算法完成:
(1) 用最少时间在表中查找数值为x的元素。
(2)若找到,将其与后继元素位置相交换。
(3)若找不到,将其插入表中并使表中元素仍递增有序。
void findXandSwapFromLinkedList(LinkedList& L, ElemType x) {LNode* p = L;LNode* pre = NULL;while (p && p->data < x) {pre = p;p = p->next;}// 找到了元素值为x的节点if (p && p->data == x) {// 如果该节点不是最后一个节点if (p->next) {// 交换当前节点与后继节点的位置ElemType temp = p->data;p->data = p->next->data;p->next->data = temp;}}// 没找到元素值为x的节点,将其插入有序链表中else {LNode* node = (LNode*)malloc(sizeof(LNode));node->data = x;// 如果pre为NULL,说明x应该插入到链表头部if (!pre) {node->next = L;L = node; // 更新链表头指针}else {node->next = pre->next;pre->next = node;}}
}
30.已知三个带头结点的线性链表A、B和C中的结点均依元素值自小至大非递减排列(可能
存在两个以上值相同的结点),编写算法对A表进行如下操作:使操作后的链表A中仅留下.
三个表中均包含的数据元素的结点,且没有值相同的结点,并释放所有无用结点。限定算
法的时间复杂度为O(m+n+p),其中m. n和p分别为三个表的长度。[清华大学(15
分)]
void removeDuplicates(LinkedList& A, LinkedList B, LinkedList C) {LNode* pa = A->next;LNode* pb = B->next;LNode* pc = C->next;LNode* tail = A;tail->next = NULL;while (pa && pb && pc) {if (pa->data < pb->data || pa->data < pc->data) {LNode* removed = pa;pa = pa->next;free(removed);}else if (pa->data > pb->data || pa->data > pc->data) {// A链表当前节点的值大于B或C链表当前节点的值,则分别移动B和C链表的指针if (pb->data < pc->data) {pb = pb->next;}else if (pb->data > pc->data) {pc = pc->next;}else {pb = pb->next;pc = pc->next;}}else {tail->next = pa;tail = pa;pa = pa->next;pb = pb->next;pc = pc->next;tail->next = NULL;}}while (pa) {LNode* removed = pa;pa = pa->next;free(removed);}
}
1.3循环单链表
typedef int ElemType;typedef struct LNode {ElemType data;LNode* next;
}LNode;
typedef struct LinkedList {LNode* head; //头结点LNode* tail; //尾结点int length; //链表长度
}LinkedList;//初始化单链表
void initLinkedList(LinkedList& L) {L.head = NULL; //初始化头指针尾指针为空长度为0L.tail = NULL;L.length = 0;
}
//头插法创建单链表
void createLinkedListWithHead(LinkedList& L) {ElemType x;scanf("%d", &x);while (x != 999) {LNode* node = (LNode*)malloc(sizeof(LNode));node->data = x;if (L.head == NULL) { //如果是创建的第一个元素,头指针尾指针都指向当前元素L.head = node;L.tail = node;}else {node->next = L.head; //如果不是第一个元素和普通头插法一样L.head = node;}L.tail->next = L.head; //每次要把尾指针的next指向头指针L.length++; //维护表长正确性scanf("%d", &x);}
}
//尾插法创建单链表
void createLinkedListWithTail(LinkedList& L) {ElemType x;scanf("%d", &x);while (x != 999) {LNode* node = (LNode*)malloc(sizeof(LNode));node->data = x; if (L.head == NULL) { //如果是创建的第一个结点L.head = node;L.tail = node;}else {L.tail->next = node; //尾指针的next指向新创建的结点L.tail = node; //尾指针来到新结点处}L.tail->next = L.head; //每次尾指针的next指向头指针L.length++;scanf("%d", &x);}
}
//返回第pos个位置上的结点
LNode* getNode(LinkedList L, int pos) {//位置不对或表位空返回NULLif (pos < 0 || L.head == NULL) {return NULL;}if (pos == 1) { //如果要找第一个位置结点元素直接返回return L.head;}int i = 1;LNode* p = L.head->next; //从第二个结点开始查找while (p != L.head && i < pos - 1) {p = p->next;i++;}return p == L.head ? NULL : p; //如果再一次来到头指针即第一个元素位置证明没找到返回NULL,否则返回找到的p
}
//向表尾插入元素e
void insertElemToTail(LinkedList& L, ElemType e) {LNode* node = (LNode*)malloc(sizeof(LNode));node->data = e;if (L.head == NULL) { //如果链表为空,创建新结点让头尾指针都指向新结点L.head = node;L.tail = node;}else { L.tail->next = node;L.tail = node;}L.tail->next = L.head;L.length++;
}
//打印链表
void printLinkedList(LinkedList L) {LNode* p = L.head;//分开打印是因为刚开始p的位置即为L.head,当p第二次来到L.head时说明已经遍历一轮了,为了避免下面while刚进去就退出条件的情况,如果是带头结点的循环单链表不需要单独分开打印if (p) {printf("%d->", p->data);p = p->next;}while (p != L.head) {printf("%d->", p->data);p = p->next;}printf("\n");
}
1.4双链表
typedef int ElemType;
typedef struct DNode {ElemType data; //数据域DNode* prior; //前驱指针域DNode* next; //后继指针域
}DNode,*DLinkedList;//双链表的初始化(带头结点)
void initDLinkedList(DLinkedList& L){L = (DNode*)malloc(sizeof(DNode));if (L) {L->next = NULL;L->prior = NULL;L->data = 0;}
}
//头插法创建单链表
void createDListWithHead(DLinkedList& L){ElemType x;scanf("%d", &x);while (x!=999){DNode* node = (DNode*)malloc(sizeof(DNode));if (node) {node->data = x;node->next = L->next;if (L->next != NULL) {L->next->prior = node;}L->next = node;node->prior = L;L->data++;}scanf("%d", &x);}
}//尾插法创建单链表
void createDListWithTail(DLinkedList& L) {ElemType x;scanf("%d", &x);DNode* tail = L;while (x != 999) {DNode* node = (DNode*)malloc(sizeof(DNode));if (node) {node->data = x;node->next = NULL;tail->next = node;node->prior = tail;tail = node;L->data++;}scanf("%d", &x);}
}//返回第pos个位置上的结点
DNode* getNode(DLinkedList L, int pos) {//检查pos是否合法,data是链表的长度if (pos<0 || pos>L->data) {return NULL;}int i = 0;DNode* p = L;while (p && i < pos) {p = p->next;i++;}return p;
}
//在第pos个位置插入元素e
void insertElem(DLinkedList& L, int pos, ElemType e) {if (pos<1 || pos>L->data + 1) {return;}DNode* node = (DNode*)malloc(sizeof(DNode));if (node) {node->data = e;DNode* pre = getNode(L, pos - 1);node->next = pre->next;if (pos != L->data + 1) {pre->next->prior = node;}pre->next = node;node->prior = pre;L->data++;}}
//删除第pos个位置上的元素。并用e接收被删除元素值
void deleteElem(DLinkedList& L, int pos, ElemType& e) {if (pos<1 || pos>L->data) {return;}DNode* pre = getNode(L, pos - 1);DNode* removed = getNode(L, pos);if (pos == L->data) {pre->next = NULL;}else {pre->next = removed->next;removed->next->prior = pre;}e = removed->data;free(removed);L->data--;
}
//打印双链表
void printDList(DLinkedList L) {DNode* p = L->next;if (p == NULL) {return;}printf("%d->", p->data);printDList(p);
}