2.数据结构C语言-带头结点的单链表

2.1单链表

        单链表是链表的一种,是线性表的链式存储结构。其逻辑结构是线性的,物理结构则是随机性存储的。

        习惯上用LinkList 声明头指针变量,用Node*声明节点指针变量,实际上他们是等价的。

2.2带头结点的单链表初始化函数中的注意点

        函数实现如下:

void InitList(LinkList* L)                   /*初始化单链表*/  
{                                             /*形参为指向表头指针的指针变量,这样才能通过传址 
                                             参数将函数结果带出*/
	*L = (LinkList)malloc(sizeof(struct SListNode));   /*建立头结点*/
	if ((*L) == NULL)                      
	{
		printf("\n内存分配不成功!\n");
		return ERROR;
	}                                    /*考虑到内存分配失败*/
	(*L)->next = NULL;                   /*将表头的置空*/
}                          /*InitList */

        要注意的地方是,函数的形参应该是LinkList*类型,等价于Node**类型,它是指向指针的指针。否则函数初始化的结果无法带出,将指向头结点的指针的地址作为实参,这里是指针类型的传址调用。

        加上判断malloc是否成功的语句可以解除取消对NULL指针的引用warning。

2.3建立链表时的getchar函数问题

        在使用getchar函数之前要用rewind(stdin);语句清除输入缓冲区,防止读取缓冲区中残留的字符(一般是回车符)。

2.4注意指针仅用于遍历和用于改变结点时的表达式有所不同

        Node* p;

        p = xxx;        此时仅仅改变p内存储的地址,也就是只改变p本身

        p->xxx = xxx;        此时改变了p所指向的结点的内容。 

2.5注意使用free函数释放掉malloc过的不用的空间

2.6代码实现(详细的思路和编写时犯过的错误见注释:)

/*头文件、单链表存储结构和各种操作的声明及简单说明*/
#include<stdio.h>
#include<stdlib.h>
#define ERROR -1
#define OK 1

typedef char ElemType;

typedef struct SListNode {
	ElemType data;
	struct SListNode* next;
}Node,*LinkList;


void InitList(LinkList *L);                   /*初始化单链表*/
void PrintList(LinkList L);                  /*打印单链表*/
void CreatFromHead(LinkList L);              /*头插法建立单链表*/
void CreatFromTail(LinkList L);              /*尾插法建立单链表*/
Node* GetElem(LinkList L, int i);            /*在单链表中查找第i个结点*/
Node* Locate(LinkList L, ElemType key);      /*在单链表中查找值为key的结点并返回其地址*/
int ListLength(LinkList L);                  /*求单链表的长度*/
int InsList(LinkList L, int i, ElemType e);  /*在单链表中第i个位置插入值为e的新结点*/
int DelList(LinkList L, int i, ElemType* e); /*删除单链表中第i个位置的结点*/
LinkList MergeList(LinkList LA, LinkList LB);/*尾插法合并两个有序非降序单链表为升序*/


/*各种操作的具体实现和详细注释*/
#include"SLinkList.h"
void InitList(LinkList* L)                   /*初始化单链表*/  
{                                             /*形参为指向表头指针的指针变量,这样才能通过传址参数将函数结果带出*/
	*L = (LinkList)malloc(sizeof(struct SListNode));   /*建立头结点*/
	if ((*L) == NULL)                      
	{
		printf("\n内存分配不成功!\n");
		return ERROR;
	}                                    /*考虑到内存分配失败*/
	(*L)->next = NULL;                   /*将表头的置空*/
}                          /*InitList */
void PrintList(LinkList L)                  /*打印单链表*/
{
	Node* cur = L->next;                  /*cur指针用于遍历单链表打印*/
	while (cur)                           /*当cur为空时,说明上一个结点的next指针为空,即单链 
                                            表遍历完成*/
	{
		printf("%c ", cur->data);
		cur = cur->next;
	}                         /*while */
	printf("\n");
}                        /*PrintList */
void CreatFromHead(LinkList L)              /*头插法建立单链表*/
{
	Node* s = NULL;                 
	char c;                     /*用于接收输入字符*/
	int flag = 1;                /*设置一个标志位,当输入‘$’时置0*/
	rewind(stdin);               /*清除输入缓冲区,防止读取缓冲区中残留的字符*/
	while (flag)
	{
		c = getchar();
		if (c != '$')
		{
			s = (Node*)malloc(sizeof(Node));      /*新建一个结点*/
			if (s == NULL)
			{
				printf("\n内存分配不成功!\n");
				return ERROR;
			}
			s->data = c;                      /*将数据写入新建结点*/
			s->next = L->next;              /*将新建结点的next指针指向原来的第一个结点*/
			L->next = s;                     /*将头结点next指针指向新建结点*/
		}
		else
		{
			flag = 0;
		}
	}       /*while */
}           /*CreatFromHead */
void CreatFromTail(LinkList L)              /*尾插法建立单链表*/
{
	Node* s;
	Node* cur = L;               /*声明一个用于指向单链表队尾的指针*/
	char c;
	int flag = 1;
	rewind(stdin);                 /*清除输入缓冲区,防止读取缓冲区中残留的字符*/
	/*while (cur->next)
	{
		cur = cur->next;
	}*/                    /*尾插法建表则初始表尾就在表头*/
	while (flag)
	{
		c = getchar();
		if (c != '$')
		{
			s = (Node*)malloc(sizeof(Node));
			if (s == NULL)
			{
				printf("\n内存分配不成功!\n");
				return ERROR;
			}
			s->data = c;         
			cur->next = s;      /*将原队尾结点的next指针指向新建结点*/
			//s->next = NULL;  /*如需继续插入则无须将当前结点指针域清空*/
			cur = s;           /*cur指针指向新的队尾*/
		}
		else
		{
			flag = 0;           
			cur->next = NULL;     /*结束建表时将队尾结点的next指针清空*/
		}
	}       /*while */
}           /*CreateFromTail */
Node* GetElem(LinkList L, int i)            /*在单链表中查找第i个结点*/
{
	Node* cur = L;
	int j = 0;
	if (i < 1)                 /*查找的结点序号书必须是从1开始的连续正整数*/
	{
		printf("\n结点序号是从1开始的连续正整数!\n");
		return ERROR;
	}
	//while (cur != NULL && j < i)         /*终止循环条件为结点指针为空或j == i */
	while (cur->next != NULL && j < i)      /*终止循环条件为结点next指针为空或j == i,少查找 
                                             一次*/
	{
		cur = cur->next;
		j++;
	}           /*while */
	/*if (cur)                 
	{
		return cur;
	}*/                        /*循环跳出时cur不为空说明循环跳出时i == j即找到了第i号结点,否 
                               则就是不存在第i号结点*/
	if (i == j)                /*查找到最后一个结点时i == j,说明刚好最后一个结点就是要找结 
                                点,否则就是不存在第i号结点*/
	{
		return cur;
	}
	else
	{
		printf("\n查无此结点!\n");
		return ERROR;
	}
}               /*GetElem */
Node* Locate(LinkList L, ElemType key)      /*在单链表中查找值为key的结点并返回其地址*/
{
	Node* cur = L;
	/*while (cur->next != NULL && cur->data != key)
	{
		cur = cur->next;
	}
	if (cur->data == key)
	{
		return cur;
	}*/
	while (cur)           /*遍历单链表*/
	{
		/*if (cur->data == key)
		{
			return cur;
		}
		cur = cur->next;*/
		if (cur->data != key)        /*值不相同时继续遍历,相同则返回结点地址*/
		{
			cur = cur->next;
		}
		else
		{
			return cur;
		}
	}
	printf("\n单链表中没有结点的数据域存放%c!\n",key);    /*遍历结束仍没有相匹配的结点,则说明 
                                                          单链表中没有结点的数据域存放与key 
                                                           相同的值*/
	exit(ERROR);
}
int ListLength(LinkList L)                  /*求单链表的长度*/
{
	int i = 0;
	Node* cur = L;
	while (cur->next != NULL)       /*i随着cur一起变化,i的数值即为结点的序号(不包含头结点), 
                                       故当cur指向最后一个结点时的i值即为单链表长度*/
	{
		i++;
		cur = cur->next;
	}  
	return i;
}   /*ListLength*/
int InsList(LinkList L, int i, ElemType e)  /*在单链表中第i个位置插入值为e的新结点*/
{
	int j = 0;
	Node* s;
	Node* prev = L;
	if (i < 1)
	{
		printf("\n插入位置不合法!\n");
		return ERROR;
	}
	while (prev && j < i - 1)        /*找到第i个结点的前驱结点*/
	{
		j++;
		prev = prev->next;
	}
	if (prev)                     /*如果第i个结点的前驱结点不为空*/
	{
		s = (Node*)malloc(sizeof(Node));      
		if (s == NULL)
		{
			printf("\n内存分配不成功!\n");
			return ERROR;
		}
		s->data = e;                    
		s->next = prev->next;           /*将新结点的next指针指向原第i个结点*/
		prev->next = s;            /*将原第i个结点的前驱结点的next指针指向新结点*/
		return OK;
	}
	printf("\n插入位置不合法!\n");
	return ERROR;
}
int DelList(LinkList L, int i, ElemType* e) /*删除单链表中第i个位置的结点*/
{
	int j = 0;
	Node* r;                          /*用于保存被删除结点的地址*/
	Node* prev = L;
	if (i < 1)
	{
		printf("\n删除位置不合法!\n");
		return ERROR;
	}
	while (prev->next && j < i - 1)        /*找到第i个结点的前驱结点,注意删除结点的前驱结点的 
                                            合法范围为0到倒数第二个结点*/
	{
		j++;
		prev = prev->next;
	}
	if (prev->next)
	{
		r = prev->next;              /*保存被删除结点的地址*/
		prev->next = r->next;        /*修改指针,将被删除结点的先驱结点的next指针指向被删除结点的后驱结点*/
		*e = r->data;                /*保存一下被删除的数据*/
		free(r);                    /*释放被删除结点占用的空间,malloc的用法*/
		return OK;
	}
	printf("\n删除位置不合法!\n");
	return ERROR;
}
LinkList MergeList(LinkList LA, LinkList LB)/*尾插法合并两个有序非降序单链表仍为有序非降序*/
{
	//LinkList LC = NULL;           
	//LC = (LinkList)malloc(sizeof(Node));
	//if (LC == NULL)
	//{
	//	printf("\n内存分配不成功!\n");
	//	return ERROR;
	//}
	//LC->next = NULL;                /*创建链表LC,初始化其next指针为空。考虑到InitList函数 
                                       对本函数可见,也可使用函数进行初始化*/
	//Node* pa = LA->next;            
	//Node* pb = LB->next;   
	///*Node* pc = LC->next;*/        /*错误,这样创建无法改变LC->next的指向,也就无法辅助尾 
                                       插法创建LC链表*/
	//Node* pc = LC;                  /*创建用于移动的指针*/
	//while (pa && pb)                /*遍历LA和LB,比大小后把数据域较小的结点尾插到LC中*/
	//{
	//	if (pa->data <= pb->data)
	//	{
	// 	    /*pc = pa*/            /*错误,这样只改变pc的指向,而LC->next的指向一直为空*/
	//		pc->next = pa;
	//		pc = pc->next;       /*尾插*/
	//		pa = pa->next;       /*pa继续遍历LA*/
	//	}
	//	else
	//	{
	// 	    /*pc = pa*/            /*同样的错误*/
	//		pc->next = pb;            
	//		pb = pb->next;              /*尾插*/
	//		pc = pc->next;            /*pb继续遍历LB*/
	//	}
	//}           /*while */
	//if (pa)
	//{
	// 	/*pc = pa*/                  /*同样的错误*/
	//	pc->next = pa;
	//}
	//if (pb)
	//{
	// 	/*pc = pb*/                   /*同样的错误*/
	//	pc->next = pb;
	//}                              /*将未遍历完的链表的剩余部分插到LC末尾*/
	//free(LA);             
	//free(LB);                /*释放掉LA和LB两个头结点的空间*/
	//return LC;                /*返回合并后的链表LC的地址*/
	/*以上算法可以优化如下:*/
	LinkList LC;
	LC = LA;
	Node* pa = LA->next;
	Node* pb = LB->next;
	Node* pc = LC;
	while (pa && pb)                /*遍历LA和LB,比大小后把数据域较小的结点尾插到LC中*/
	{
		if (pa->data <= pb->data)
		{
			pc->next = pa;
			pc = pc->next;       /*尾插*/
			pa = pa->next;       /*pa继续遍历LA*/
		}
		else
		{
			pc->next = pb;            
			pb = pb->next;              /*尾插*/
			pc = pc->next;            /*pb继续遍历LB*/
		}
	}           /*while */
	if (pa)
	{
		pc->next = pa;
	}
	if (pb)
	{
		pc->next = pb;
	}                              /*将未遍历完的链表的剩余部分插到LC末尾*/
	free(LB);                /*释放掉LB的头结点的空间,此时LA头结点就是LC头结点,所以不用释放 
                              LA的头结点*/
	return LC;                             /*返回合并后的链表LC的地址*/
	/*优化后的算法少开辟了一个LC的头结点空间*/
}       /*MergeList */


/*主函数和测试程序*/
#include"SLinkList.h"
TestSLinkList()                   /*自定义各种操作的测试程序*/
{
	//char c;
	//int num;
	//LinkList LA;
	//LinkList LB;
	//LinkList LC;
	//InitList(&LA);
	//InitList(&LB);
	//CreatFromHead(L);
	//CreatFromTail(LA);
	//CreatFromTail(LB);
	//PrintList(LA);
	//PrintList(LB);
	//printf("\n输入要查找的结点序号:");
	//scanf_s("%d", &num);
	//printf("\n第%d号结点的数据域中数据元素为%c。\n", num, GetElem(L, num)->data);

	//printf("\n输入要查找的数据元素:");
	rewind(stdin);
	//rewind(stdin);  /*清除输入缓冲区*/
	//c = getchar();
	//printf("\n%c存放在单链表中地址为“%p”的结点的数据域中。\n",c,Locate(L,c));

	//InsList(L, 3, 'c');
	//DelList(L, 3, &c);
	//LC = MergeList(LA, LB);
	//PrintList(LC);

}
int main()
{
	TestSLinkList();          /*主函数调用测试函数*/
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值