1. 顺序表 SeqList
#include<stdio.h>
#define ElemType int
#define OK 1
#define Error -1
const int Maxsize = 100;
typedef struct{
ElemType elem[Maxsize]; // 数组元素
int last; // 数组最后一个元素下标,空表为-1
}SeqList;
// 顺序表基操1 按值查找e
int seqFind(SeqList L, ElemType e){
int i = 0; // 遍历元素下标初值
while(L.elem[i]!=e && i <= L.last) i++; // 遍历顺序表,直到找到待查元素
if(i <= L.last) return L.elem[i]; // 查找成功
else return Error; // 查找失败
}
// 顺序表基操2 在第i个元素插入e操作
int seqInsert(SeqList *L, int i, ElemType e)
{
if(i < 1 || i > L->last + 2) return Error; // 检验插入位置合法性 注意i是序号
if(L->last >= Maxsize - 1) return Error; // 检验长度合法性
for(int k = L->last; k >= i-1; k -- ) L->elem[k + 1] = L->elem[k]; // 第i个位置及以后向后移动一位
L->elem[i - 1] = e; // 第i个留出的空位补数
L->last ++ ; // 改变最后一个元素的参数
return OK;
}
// 顺序表基操3 删除第i个元素,返回删除值e
int seqDelete(SeqList *L, int i, ElemType *e){
if (i < 1 || i > L->last + 1) return Error; // 合法性
*e = L->elem[i - 1]; // 删除元素放入指针e的变量中
for(int k = i; k <= L->last; k ++) L->elem[k + 1] = L->elem[k];
L->last -- ;
return OK;
}
// 顺序表基操4 合并两个顺序表 双指针的思想
int seqMerge(SeqList *La, SeqList *Lb, SeqList *Lc){
int i = 0, j = 0, k = 0 ;
// 双指针分别遍历两个数组,取较小者加入到Lc数组中
while (i <= La->last && j <= Lb->last){
if(La->elem[i] <= Lb->elem[j]) Lc->elem[k++] = La->elem[i++];
else Lc->elem[k++] = Lb->elem[i++];
}
// 处理剩余的元素
while (i <= La ->last) Lc->elem[k++] = La->elem[i++];
while (j <= Lb ->last) Lc->elem[k++] = Lb->elem[j++];
return OK;
}
int main()
{
return 0 ;
}
2. 链表 LinkList
#include<stdio.h>
#include<stdlib.h>
#define ElemType char
#define OK 1
#define Error -1
const int Maxsize = 100;
typedef struct Node{
ElemType data; // 数据域
struct Node *next; // 指针域
}Node, *LinkList;
// 基操1 头插法建立单链表
LinkList CreateFromHead(){
LinkList L; // 待生成链表
Node *s; // 输入时产生的临时结点
int flag = 1; // 输入符号"$"时 设置flag为0 表示输入结束
L = (LinkList)malloc(sizeof(Node)); // 设置头节点
L->next = NULL;
while (flag){
char c = getchar();
if (c != '$'){
s = (Node *)malloc(sizeof(Node)); // 新建一个结点
s->data = c;
s->next = L->next;
L->next = s;
}
else flag = 0;
}
return L;
}
// 基操2 尾插法建立单链表
// 注意:需要额外添加一个尾指针
LinkList CreateFromTail()
{
LinkList L = (LinkList)malloc(sizeof(Node));
Node *s,*r; // 临时结点和尾指针
int flag = 1; // flag = 0 时表示输入结束
L->next = NULL;
r = L;
while (flag)
{
char c = getchar();
if(c != '$'){
s = (Node *) malloc(sizeof (Node));
s->data = c ;
s->next = r->next;
r->next = s;
r = s;
}
else flag = 0;
}
return L;
}
// 基操3 按序号查找第i个结点
Node * LinkFind (LinkList L, int i){
Node * p = L;
int j =0;
while ((p->next != NULL) && (j < i)) j ++ ; // 遍历
if( j == i ) return p; // 两种等价写法
else return NULL;
}
// 基操4 按值查找结点
Node * LinkFind2(LinkList L, ElemType key){
Node * p = L->next; // 与第一个结点比较
while (p != NULL){
if(p->data != key) p = p->next;
else break;
}
return p;
}
// 基操5 单链表第i个元素前插入元素e
void InsList(LinkList L, int i, ElemType e){
Node *pre, *s;
pre = L;
int k = 0;
// 遍历查找第i个结点,使用Pre指向
while (pre != NULL && k < i - 1){
pre = pre->next;
k ++ ;
}
if (pre == NULL) return ;
s = (Node *) malloc(sizeof(Node)); // 申请新的结点资源
s->data = e;
// 进行插入操作
s->next = pre->next;
pre->next = s;
}
// 基槽6 单链表删除第i个元素,删除元素保存到e中
void DelList(LinkList L, int i, ElemType *e){
Node *p, *r; // p 指向删除结点前驱,r指向删除结点
p = L;
int k = 0;
// 遍历查找删除结点前驱
while (p->next != NULL && k < i -1 ){
p = p->next;
k ++ ;
}
if (k != i - 1) return ;
r = p->next;
p->next = r->next;
free(r);
}
// 基操7 链表长度
int ListLength(LinkList L){
Node *p;
p = L->next;
int j = 0; // 链表长度
while(p != NULL){
p = p->next;
j ++;
}
return j ;
}
// 基操8 合并两个单增有序链表 要求在La基础上修改 不占用新空间
LinkList MergeLinkList(LinkList La, LinkList Lb){
Node *pa, *pb, *r; // 分别指向访问La Lb 的指针
LinkList Lc; // 合并后的链表
pa = La->next, pb = Lb->next; // 初始化为第一个结点
Lc = La;
Lc->next = NULL;
r = Lc; // Lc 尾指针
// 选择较小的使用尾插法插入Lc后
while(pa != NULL && pb != NULL){
if(pa->data < pb->data){
r->next = pb;
r = pb;
pa = pa->next;
}
else{
r->next = pb;
r = pb ;
pb = pb->next;
}
}
// 处理后续元素,注意 只要尾指针指向即可
if (pa != NULL) r->next = pa;
else r->next = pb;
free(Lb),free(La);
return Lc;
}
int main()
{
return 0 ;
}
2.1 循环链表
// 循环链表基操 初始化
void InitCLinkList(LinkList *CL)
{
*CL = (LinkList)malloc(sizeof(Node));
(*CL)->next = *CL; //初始状态是头节点指向自己
}
void CreateCLinkList(LinkList CL){
Node *r, *s;
r = CL; // 依旧是使用到了一个尾插法的操作后续不再赘述
int flag = 1;
while (flag){
char c = getchar();
if(c != '$'){
s = (Node *)malloc(sizeof(Node));
s->data = c;
s->next = r->next;
r->next = s;
r = s;
}
else flag = 0;
}
r->next = CL; // 最后一个结点指向头节点
}
LinkList merge(LinkList La, LinkList Lb){
/* 算法功能:使用两个头指针的循环单链表首尾连接 */
Node *p, *q;
p = La, q = Lb;
while (p->next != La) p = p->next;
while (q->next != Lb) q = q->next;
q->next = La;
p->next = Lb->next;
free(Lb);
return La;
}
2.2 双向链表
// 双向链表
typedef struct DNode{
ElemType data;
struct DNode *piror, *next;
}DNode, *DoubleList;
// 双向链表基操1 :前插
void InsDLink(DoubleList L, int i, ElemType e){
DNode *s, *pre = L; // s是临时结点,pre是插入结点前驱
int k = 0;
while (pre != NULL && k < i - 1 ){
pre = pre->next;
k ++;
}
if(k != i - 1) return ;
s = (DNode *)malloc(sizeof(DNode));
if(s){
s->data = e;
s->next = pre->next;
s->piror = pre;
pre->next->piror = s;
pre->next = s;
}
}
// 双向链表基操2 : 删除第i个元素
int DelDLink(DoubleList L, int i, ElemType *e){
DNode *p = L->next; // 待删除结点
int k = 0;
while (p != NULL && k < i) p = p->next, k ++ ;
if (k != i - 1) return Error; // 查找失败
// 删除操作
*e = p->data;
p->piror->next = p->next;
p->next->piror = p->piror;
free(p);
return OK;
}
练习题
练习1 顺序表删除元素(双指针法)
// 练习1 顺序表L中删除所有值为x的元素,要求时间复杂度O(n) 空间复杂度O(1)
// 不可以用原先的先遍历再插入的思想
// 复杂点在于遍历,能不能一起遍历?能!
// 双指针的思想:一个指针负责遍历,另一个指针负责原地构建新表
void delx(SeqList *L, int x){
int i = 0, j = 0;
// 外层while循环负责遍历
while (i < L->last ){
// 根据原表中与待删除元素是否相同来构造新表
if (L->elem[i] != x) L->elem[j++] = L->elem[i++];
else i ++ ; // 遇到删除元素,只需调整遍历下标即可,新表无须改动
}
L->last = j - 1;
}
法二--偏移量法
// 对比实验 删除顺序表介于x和y之间的元素
// 是上面的一个引申,那么我们可以延申这样一个双指针的思想
bool DelList(SeqList &L, int x, int y){
int k = 0;
if(x > y || L.last == -1) return Error; // 非法输入的情况
for(int i = 0; i <= L.last; i++){
// 这里换了一种思路,即用了一个k的偏移量来记录了待插入的位置
if(L.elem[i] >= x && L.elem[i] <= y) k ++;
else L.elem[i - k] = L.elem[i]; // 前移k个元素
}
L.last -= k;
return OK;
}
引申--链表中的双指针-指针备份
// 对比实验: 删除链表介于x与y之间的元素
// 这个也很简单了,换汤不换药,明确了双指针遍历(pre和p)的思想后 改改条件就可以完成这个任务了
LinkList delXtoY(LinkList L, int x, int y){
Node * pre = L; // p的前驱,以备后续删除结点操作,初始值设为头节点
Node * p = L->next; // p结点,是负责遍历链表的操作对象,后续是作为删除结点的
if(L->next == L) return L; //空表无须操作
while(p != NULL){
// 满足条件 执行单链表删除操作
if (p->data >= x && p->data <=y){
Node * q = p;
p = p->next;
pre->next = q->next;
free(q);
continue;
}
else{
pre = p;
p=p->next;
}
}
return L;
}
// 引申1 删除链表最大的结点
// 对于链表的增删那就很简单,只需要一个指针记录下这个最大的指针就好了
LinkList delMax(LinkList L){
Node *pre = L; // pre指向的是p的前一个结点地址
Node *p = L->next; // p指针旨在遍历链表,初始值为头节点的next域
Node *q = NULL; // q指针负责记录最大结点的位置
Node *maxpre = NULL; // maxpre负责记录最大结点的前驱
int maxn = -1 ;
while(p != NULL){
if(p->data > maxn){
q = p;
maxpre = pre;
maxn = p->data; // 更新最大值
}
pre = pre->next;
p = p->next;
}
// 如果找到了最大结点,那么删除
if(q != NULL){
pre->next = q->next;
free(q);
}
return L;
}
练习2 链表原地逆置(双指针)
// 练习2 带头结点单链表 就地逆置问题
// 因为只能在原有基础上,顺序表可以基于交换。但是链式要用的话,只能遍历找 达不到O(n)要求
// 咱继续沿用双指针思路,应用头插法的感jio来完成这个任务
void ReverseList(LinkList L){
/* 逆置带头节点的单链表 L */
Node *p = L->next; // p是链表当前处理的结点
L->next = NULL; // 建立新表
while (p != NULL){
Node *q = p->next ; // q是处理结点的下一个
p->next = L->next;
L->next = p; // L指向所谓的“表头”
p = q; // 接着处理下一个
}
}
引申:双指针中快慢指针问题
// 补充练习2.1 如何在O(n)要求下找到中间结点的位置(双指针-快慢指针问题)
// 两个指针p,q,然后对这两个指针对整个链表遍历,而遍历的过程中P移动一个结点,q移动两个结点
// 最后q==NULL时,p的位置就是中间结点的位置
// 补充练习2.2 如何O(n)找倒数第k个结点的位置(双指针-快慢指针问题)
// 同样提供两个指针p,q:p指向第一个结点,q指向第k+1个结点.这样,p和q之间始终相差k个元素
// 当q==NULL时,p的位置就是倒数第k个位置
练习3 单链表的一次划分(删除+头插)
// 练习3 带头节点的单链表 以第一个元素为基准进行一次划分(小的在他前面 大的在他后面)
// 思路较简单,我们只需要将小的结点删除+移动到前面即可
// 可以分成两个步骤:1.对小结点进行删除基操 2.用头插法将删除结点接到头节点
void LinkPartition(LinkList L){
if (L->next == NULL) return ; // 空表无须处理
Node *p1 = L->next; // 基准指针
Node *pre = p1; // 初始值,待删除结点前驱
Node *p = p1->next; // 初始值,负责探寻比基准元素小的指针
while (p != NULL){
if(p->data >= p1->data){
pre = p; // 修改待删除结点的前驱
p = p->next; // 指针后移
}
else{
pre->next = p->next; // 删除结点
p->next = L->next;
L->next = p;
p = p->next;
}
}
}
练习4 二进制加法(查找+头插)
// 练习4 二进制加法 一个结点存储一个二进制位
// 存储结构:考虑头插法的思路,表尾是二进制最低位,表头是二进制最高位
// 逻辑结构:由于是二进制运算,应当从表尾开始遍历找第一个为0的点,然后他后面的结点全部取反
// 所以我们的思路是从前往后找最后一个为0的点,将其变为1,然后后面遍历的时候全部取反
// 关注一个特殊情况:如果链表遍历完发现是全1,那么我需要新建一个结点头插进去,再将所有结点赋为0
void BinAdd(LinkList L){
Node *q, *r;
q = L->next; // 用来遍历链表的指针,初始值指向头节点后一个
r = L; // 记录最后一个不为0 的指针,初始值是头节点
while (q != NULL){
if(q->data == 0) r = q;
q = q->next;
}
// 如果r指向的不是L,说明查找到了为0的结点,只需修改值为1
if(r != L){
r->data = 1;
}
// 如果指向的是L,说明链表全是1,那么需要新建值为1的结点插入表头
else{
Node *s = (Node *)malloc(sizeof(Node));
s->data = 1;
s->next = L->next;
L->next = s;
r = s;
}
// 确定好了修改为1的指针后,将后续的结点全部反转即可
r = r->next ;
while(r != NULL){
r->data = 1 - r->data;
r = r->next;
}
}