数据结构ky备战Day1 - 线性表

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;
}

image-20231004224440122

法二--偏移量法

// 对比实验 删除顺序表介于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; // 接着处理下一个
	}
}

image-20231004224512811

引申:双指针中快慢指针问题

// 补充练习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;
	}
}
posted @ 2023-10-03 22:34  yuezi2048  阅读(22)  评论(0编辑  收藏  举报