定义
   
线性表的链式存储方式称为单链表,它是指通过一组任意的存储单元来存储线性表中的数据元素。
为了实现数据元素之间的线性关系,所以每个结点中除了存储自身的值以外,还会存放一个指向后继的指针。
具体的代码如下:
typedef struct LNode{
	int data;//结点的数据
	LNode* Next;//指向下一结点的指针
}LNode,*LinkList;//前者为结点,后者为链表(只是两种不同表述方式)
    
    
    操作
   
在单链表的具体实现中,有两种实现方式,一种是带头结点的单链表,一种是不带头结点的单链表。
两者的共同点在于,都有一个头指针用于指向链表的第一个结点。
两者的区别在于,带头结点的单链表会有一个头结点,这个头结点的Next指针指向单链表的第一个结点,所以头指针是指向头结点的;而不带头结点的单链表则是由头指针直接指向第一个结点。
因为带头结点的单链表在代码实现的时候更加简便,所以本文以带头结点的形式来实现单链表各种操作。
    
    
    初始化
   
bool InitList(LinkList& L)
{
	L = (LNode*)malloc(sizeof(LNode));//创建头结点
	if (L == NULL)
	{
		cout << "内存不足分配失败!" << endl;
		return false;
	}
	else
	{
		L->next = NULL;//初始化的链表中没有结点,所以指向NULL
		return true;
	}
}
在初始化时,要注意使用malloc函数的判空,因为使用malloc时可能会出现失败的情况,加入判空可以增加代码的健壮性。
    
    
    结点前插
   
    给定链表的一个结点,实现在此结点的前面插入一个结点,有两种实现方式。
    
    第一,从头开始找到该结点的前驱结点实现插入(这种实现方式需要我们知道头指针,并且时间复杂度为O(n))
    
    第二,原地前插
   
bool InsertPriorNode(LNode* p, int e) {//在p结点前面插入元素e
	if (p == NULL) {//判断p结点是否为空,如果为空是不能前插的
		return false;
	}
	LNode* L = (LNode*)malloc(sizeof(LNode));
	if (L == NULL) {
		cout << "内存分配失败" << endl;
		return false;
	}
	else {
		L->next = p->next;//新结点连入
		p->next = L;//前链连接上新结点
		L->data = p->data;//数据交换,实现前插
		p->data = e;
		return true;
	}
}
在这种前插方式下,时间复杂度为O(1),而且不需要知道头指针
如果给定的是需要前插的不是值,而是结点,具体的实现代码如下:
bool InsertPriorNode(LNode* p, LNode *s) {//在p结点前面插入结点s
	if ((p == NULL)||(s == NULL)) {
		return false;
	}
	else {
		s->next = p->next;//同样采用后插方式
		p->next = s;
		int temp;//具体实现时的数据类型可能并不是int,灵活变换即可
		temp = s->data;//交换数据实现前插
		s->data = p->data;
		p->data = temp;
		return true;
	}
}
    
    
    后插
   
给定链表的一个结点,实现在此结点的后面插入一个节点,实现方式与前插类似,甚至更简单直观。
bool InsertNextNode(LNode* p, int e) {//在p结点之后插入元素e
	if (p == NULL) {
		return false;
	}
	LNode* L = (LNode*)malloc(sizeof(LNode));
	if (L == NULL) {
		cout << "内存分配失败" << endl;
		return false;
	}
	else {
		L->next = p->next;
		p->next = L;
		L->data = e;
		return true;
	}
}
如果给定的是需要后插的不是值,而是结点,具体的实现代码如下:
bool InsertNextNode(LNode* p, LNode* s) {//在p结点之后插入结点s
	if ((p == NULL)||(s==NULL)) {
		return false;
	}
	else {
		s->next = p->next;
		p->next = s;
		return true;
	}
}
    
    
    查找
   
    在线性表的查找中,有两种查找方式。
    
    一种是按值查找,一种是按位查找
    
    因为是链式实现的线性表,所以这两种方式都需要遍历这个链表来查找,所以一般来说时间复杂度为O(n)
   
按值查找代码如下
LNode* LocateElem(LinkList L, int e) {
	if (L == NULL) {
		cout << "传入链表为空,查找失败!" << endl;
		return NULL;	
	}
	else {
		LNode* p;
		p = L->next;//因为带头结点,所以会指向头结点的下一个结点
		while ((p != NULL) && (p->data != e)) {
			p = p->next;
		}
		return p;
	}
}
按位查找代码如下
LNode* GetElem(LinkList L, int i) {
	int j = 1;//标记当前p指向第几个结点
	LNode* p=L->next;
	if (i == 0) {
		return L;
	}
	if (i < 1) {
		return NULL;
	}
	while ((p!=NULL)&&(j<i)) {
		p = p->next;
		j++;
	}
	return p;
}
    
    
    建立单链表
   
    建立单链表可以采用两种方式,一种是头插法,一种是尾插法
    
    头插法是每次输入的都从头部插入,尾插法是每次输入都从尾部插入
   
采用头插法可以用上面已经封装好的插入函数来实现
头插法
LinkList List_HeadInsert(LinkList& L) {
	int a = 0;
	while (true) {
		cout << "请输入元素值:";
		cin >> a;
		if (a != 9999) {
			InsertNextNode(L, a);
		}
		else {
			break;
		}
	}
	return L;
}
    尾插法
    
    每次都要找到最后元素
   
LinkList List_TailInsert(LinkList& L) {
	int a;
	LNode* p=L;
	while (p->next != NULL) {
		p = p->next;//找到最后元素
	}
	while (true) {
		cout << "请输入要插入的元素的值:";
		cin >> a;
		if (a != 9999) {
			LNode* s = (LNode*)malloc(sizeof(LNode));//创建新结点
			s->data = a;//新结点的元素值为输入的a
			p->next = s;//将新节点连上链表
			s->next = NULL;//新的末尾要指NULL,防止出错
			p = p->next;//p始终指向最后一个结点
		}
		else {
			break;
		}
	}
	return L;
}
    
    
    删除
   
    删除结点有两种方式,一种需要知道头指针,一种只需要知道当前结点的指针即可
    
    需要知道头指针的删除
   
bool ListDelete(LinkList& L, int i, int& e)
{
	LNode* p;
	p=GetElem(L, i-1);//p指向要删除的前一个位置
	if (p == NULL) {
		//说明前一个结点都不存在,那么无法删除
		return false;
	}
	else if (p->next == NULL) {
		//说明输入要删除的结点本身就不存在,不需要删除,直接成功
		return true;
	}
	else {
		LNode* q = p->next;//q指向要删除的结点
		e = q->data;//返回删除的值
		p->next = q->next;//链接断开
		free(q);//释放无用指针
		return true;
	}
}
不需要头指针的删除
bool ListDelete(LNode* p, int& e) {
	if (p == NULL) {
		//说明这个结点本身就不存在
		return false;
	}
	else if (p->next == NULL) {
		//说明这个结点后面是没有结点的,那么这种删除只能通过从头结点查找上一个结点来删除,所以会失败
		return false;
	}
	else {
		e = p->data;//返回删除的值
		LNode* s = p->next;
		p->data = s->data;
		p->next = s->next;
		free(s);
		return true;
	}
}
    
    
    求表长
   
求表长比较简单,一个while循环遍历以下就行,时间复杂度为O(n)
int GetListLength(LinkList L) {
	int length = 0;
	LNode* p = L;
	if (p == NULL) {
		//说明头结点的不存在,表还未创建
		cout << "表还未创建!" << endl;
		return -1;
	}
	else if (p->next == NULL) {
		//说明表创建了,但是没有元素,那么长度为0
		return 0;
	}
	else {
		while (p->next != NULL) {
			length++;
			p = p->next;
		}
		return length;
	}
}
    
    
    总结
   
单链表的具体实现还是比较简单的,主要是要弄清楚指针的指向,以及各种操作细节之处的先后顺序,如果顺序不当可能会导致最后结果的错误。
以上是复习数据结构带头结点的单链表时做的一些笔记,难免有遗漏与错误之处,请不吝予以指正。
 
