由于考研大部分单链表的实现方式中,“链表结构体”和“结点结构体”都是共用同一个结构,与之前的,表结构体和结点结构体分开方式的单链表,有很多地方的操作和初始化都略有不同,所以又抽空写了一版适合考研党参考的单链表。
由于带头结点的单链表更方便,所以采用的是带头结点的方式,不带头节点的和这个差不多,推荐带头节点。代码并不完善,欢迎评论指出不足^0^
完整代码如下:
LinkedList.c
#define ElemType int
#include <stdlib.h>
#include <stdio.h>
typedef struct Node{ //单链表结构体,也是结点结构体
ElemType data; //数据域
struct Node *next; //指针域
}Node,*List; //List为指向结构体Node的指针
// InitList(&L): 初始化单链表
void InitList(List *list){
//让链表指针指向,新创建的头节点
(*list) = (Node*)malloc(sizeof(Node));
//如果头节点创建失败,退出程序
if( (*list) == NULL ){ exit(1); }
//创建成功
(*list)->data = 0;//头节点数据域设置为0,也就是表长为0
(*list)->next = NULL;//头结点next置空
}
// DestroyList(&L): 销毁单链表
void DestroyList(List* list){
//已经被销毁,或还未初始化,什么也不做
if( (*list) == NULL){ return; }
// 空表,将头节点删除
if( (*list)->data == 0){
free(*list);
(*list) = NULL;
printf("删除头结点\n");
}else{//非空表,层层删除链表结点
//临时Node类型指针t,指向首元结点
Node* t = (*list)->next;
while( t!=NULL ){
t = t->next;
free( (*list)->next );
(*list)->next = t;
printf("删除1个结点\n");
}
//链表结点删除完毕,删除头结点
free(*list);
printf("删除头节点\n");
//表指针置空
(*list) = NULL;
}
}
// ListEmpty(L): 判断单链表是否为空
int ListEmpty(List list){
return list->next == NULL;
}
// ClearList(&L): 清空线性表
void ClearList(List* list){
DestroyList(list);
}
//获取表长
int GetSize(List list){
return list->data;
}
//获取链表上第i个结点的指针,没有返回NULL
Node* GetNode(List list, int i){
Node *ret = NULL;
if( i>(list->data) || i<1 ){ //下标非法
}else if( i==1 ){ //首元结点
ret = list->next;
}else{ //表中间的结点
//临时指针t,指向首元结点
Node* t = list->next;
int j;
for( j=1; j<i; j++ ){
t = t->next;
}
ret = t;
}
return ret;
}
// 表头插入元素e
void InsertHead(List list, ElemType e){
// 新建node
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = e;
newNode->next = list->next;
// 更改首元结点
list->next = newNode;
list->data ++;
}
// 删除首元结点
void DeleteHead(List list){
// 若为空表,什么也不做
if(list->data == 0){
}else{//表非空,进行删除
//临时指针指向首元结点的下一个结点
Node* t = list->next->next;
free(list->next);
printf("删除首元结点\n");
list->data --;
list->next = t;
}
}
// ListInsert(&L,i,e): 链表的第i个位置上插入元素e
void ListInsert(List list, int i, ElemType e){
if( i<1 || i>(list->data)+1 ){ //下标非法,异常退出
exit(1);
}else{
if( i==1 ){ //表头插入
InsertHead(list, e);
}else{ //表内插入
//创建新节点newNode
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = e;
//临时指针指向首元结点
Node* t = list->next;
//循环找到第i-1个结点,在其后进行插入
int j;
for(j=2; j<i; j++){
t = t->next;
}
//此时t指向要插入位置的前面
newNode->next = t->next;
t->next = newNode;
list->data ++;
}
}
}
// ListDelete(&L,i): 删除链表上第i元素并返回其值
ElemType ListDelete(List list, int i){
// 先判断下标是否越界
if(i<1 || i>list->data){ exit(1); }
ElemType ret;
if(i == 1){ //删表头
ret = list->next->data;
DeleteHead(list);
}else{ //删表中间
//拿到第i-1个Node
Node* prior = GetNode(list, i-1);
//保存返回值
ret = prior->next->data;
//临时指针t保存要删除的结点地址
Node* t = prior->next;
//更改第i-1个Node的next指针域
prior->next = prior->next->next;
//删除结点
free(t);
printf("删除第%d个结点\n", i);
list->data --;
}
return ret;
}
// GetElem(L,i,&e): 获取L表中第i个元素
ElemType GetElem(List list, int i){
return GetNode(list, i)->data;
}
//LocateElem(L,e):定位表中的元素e,看它是第几个元素,失败返回-1
int LocateElem(List list, ElemType e){
int ret = -1;
//临时指针t指向首元结点
Node* t = list->next;
int i;
for(i=1; i<=list->data; i++){
if(t->data == e){//找到了,结束循环返回下标
ret = i;
break;
}else{
t = t->next;//没找到,指针后移继续找
}
}
return ret;
}
//遍历链表
void TraverseList(List list){
//临时Node指针指向首元结点
Node* t = list->next;
printf("\n");
while(t != NULL){
printf("%d ", t->data);
t = t->next;
}
printf("\n");
}
//=============测试程序==============================================================
int main(int argc, char const *argv[])
{
// 定义一个单链表并初始化
List list = NULL;
InitList(&list);
// 添加元素
InsertHead(list, 13);
InsertHead(list, 23);
InsertHead(list, 33);
InsertHead(list, 43);
InsertHead(list, 53);
TraverseList(list);
printf("第2个元素为:%d\n", GetElem(list, 2));
printf("元素33是表中第%d个\n", LocateElem(list, 33));
ListInsert(list, 3, 333);
TraverseList(list);
ListDelete(list, 3);
printf("\n链表是否为空:%d\n", ListEmpty(list));
DeleteHead(list);
TraverseList(list);
printf("表长为:%d\n", GetSize(list));
TraverseList(list);
// 最后一定要销毁链表
DestroyList(&list);
return 0;
}
运行结果
这种实现方式的单链表,在初始化和销毁时需要用到二重指针,也可以按照以上方式,在结构体声明里使用别名,屏蔽掉二重指针来当作普通指针来使用;注意内存释放后要将链表指针置空,因为可能存在误用。