C语言实现简单的单链表

为了方便,创建头文件ElemType规定操作状态码和数据元素类型以及用于数据元素类型的匹配函数

typedef double ElemType;

//操作成功
#define OK 1
//操作错误
#define ERROR 0
//操作异常
#define OVERFLOW -2
//定义元素类型,int可使用基本数据类型和用户自定义数据类型替换,根据使用场景选择
typedef int Status ;

double absForDouble(double a);

/**
 * 求绝对值
 * @param a
 * @return 返回a的绝对值
 */
double absForDouble(double a) {
    if (a < 0)return -a;
    return a;
}

/**
 * 实现两个ElemType类型变量的匹配
 * @param e1 ElemType类型变量e1
 * @param e2 ElemType类型变量e2
 * @return 匹配结果,1表示匹配,0表示不匹配
 */
int match(ElemType e1, ElemType e2){
    if(absForDouble(e1-e2)<1e-6)
        return 1;
        return 0;
}

单链表的定义和实现

/**
 * 单链表的定义和实现
 */

/**
 * 线性表的链式存储结构
 * 什么是线性表的链式存储结构?
 * 使用一组任意的存储单元存储线性表的数据元素,这些存储单元可以是连续的,也可以是不连续的,而为了表现线性表中各元素的直接后继关系,在数据元素的基础上
 * 额外的使用指针来存储当前数据元素的直接后继的位置信息,这样的包含了数据元素的数据和数据元素的直接后继位置信息的数据结构称之为结点,而由n个结点链接
 * 成一个链表,即为线性表的链式存储结构。一般的,把线性表的链式存储结构简称为单链表或者线性链表,而结点存储数据元素本身信息的部分称之为数据域,而存储
 * 数据元素的直接后继的位置信息的部分称之为指针域。换而言之,在单链表中用以存储线性表的逻辑关系的是指针域
 */

/**
 * 单链表的定义
 * 关于LNode* 和LinkedList的区别:一般的,LinkedList表示的是指向单链表的头结点(在首元结点前预设的额外的结点,其数据域不存储任何信息)的指针,而LNode*则是指指向单链表的结点的指针。
 * 头指针:指向链表中第一个结点指针(若不预设头结点,则指向首元结点,否则指向头结点)
 * 首元结点:存储线性表第一个数据元素的结点
 * 为何预设头结点?
 * 1、便于首元结点的处理,首元结点作为头结点的直接后继
 * 2、便于头指针对空表和非空表作同一处理,无论线性表是否为空表,头指针都是指向头结点的非空指针
 */
typedef struct {
    //数据域
    ElemType data;
    //指针域,存储当前结点直接后继的地址
    struct LNode *next;
} LNode, *LinkedList;

单链表基本操作的实现

/**
 * 单链表基本操作的实现
 */

/**
 * 初始化单链表:初始化单链表L
 * 算法描述:
 * 1、新建头结点,新建失败返回OVERFLOW
 * 2、使头指针指向头结点
 * 3、置头结点的指针域为NULL
 * 4、初始化成功,返回OK
 * 5、返回前置指针head指向NULL
 * @param L 指向头指针的指针L(以实现动态内存的传递)
 * @return  操作结构状态码
 */
Status initLinkedList(LinkedList *L) {
    //新建头结点
    LNode *head = (LNode *) malloc(sizeof(LNode));
    //新建失败返回OVERFLOW
    if (!head)return ERROR;
    //使头指针指向头结点
    *L = head;
    //置头结点的指针域为NULL
    head->next = NULL;
    head = NULL;
    //初始化成功,返回OK
    return OK;
}

/**
 * 取值:取单链表L上序号i的结点的数据域并保存到指针e所指向的内存单元
 * 算法描述:
 * 1、定义指向当前结点的指针p,使p指向首元结点
 * 2、定义计数器j,记录由指针p首元结点移动到i结点移动的次数,初始值为零(首元结点移动到首元结点移动零次)
 * 3、循环使指针p后移,计数器自增,直至p指向NULL(表示访问了n+1个元素,n为单链表当前结点个数)或者j = i -1(表示移动了i-1次,如果i合法,p现在指向结点i)
 * 4、判断i是否合法,i取值范围是从1到n的整数,若循环结束时,p指向NULL表示访问了n个结点仍然未找到结点i,i>n,若j > i -1表示未移动到i结点,因为i移动到i结点一定需要一定i-1次,除非i<1,i不合法
 * 5、i不合法返回ERROR
 * 6、i合法,使用指针变量e指向的存储单元保存结点i数据域的信息
 * 7、返回OK
 * 8、返回前置指针p指向NULL
 * @param L 单链表L的头指针
 * @param i 想要访问的结点序号i
 * @param e 用来保存i结点数据域的变量的指针e
 * @return 操作结果状态码
 */
Status getElem(LinkedList L, int i, ElemType *e) {
    //定义指向当前结点的指针p,使p指向首元结点
    LNode *p = L->next;
    //定义计数器j,记录由指针p首元结点移动到i结点移动的次数,初始值为零(首元结点移动到首元结点移动零次)
    int j = 0;
    //循环使指针p后移,计数器自增,直至p指向NULL(表示访问了n+1个元素,n为单链表当前结点个数)或者j = i -1(表示移动了i-1次,如果i合法,p现在指向结点i)
    while (p && j < i - 1) {
        p = p->next;
        ++j;
    }
    //判断i是否合法,i取值范围是从1到n的整数
    // 若循环结束时,p指向NULL表示访问了n个结点仍然未找到结点i,i>n
    // 若j > i -1表示未移动到i结点,因为i移动到i结点一定需要一定i-1次,除非i<1,i不合法
    if (!p || j > i - 1) {
        //i不合法返回ERROR
        return ERROR;
    }
    //i合法,使用指针变量e指向的存储单元保存结点i数据域的信息
    *e = p->data;
    //返回OK
    p = NULL;
    return OK;
}

/**
 * 查找:查找单链表L上数据域与参数e匹配的结点的结点序号和地址
 * 算法描述:
 * 1、定义指向当前结点的指针p,使其指向单链表L的首元结点
 * 2、定义计数器j记录结点序号,初始值为1
 * 3、循环改变指针p的指向并修改计数器j的值直至p=NULL(表示当前结点序号n+1,n时单链表L的当前长度,即不存在数据域和e匹配的结点,查找失败),或者当前结点数据域与e匹配,表示查找成功
 * 4、判断循环结束的原因,若p=NULL,则说明查找失败,设置匹配的结点序号为-1,返回ERROR,表示查找失败,否则查找成功,计数器j的值即结点序号,返回OK
 * 5、返回前置指针p指向NULL
 * @param L 指针单链表头指针L
 * @param e 查找的数据参数e
 * @param locate 指向符合条件的结点的指针locate
 * @param i 合条件的结点的序号i
 * @return
 */
Status indexOf(LinkedList L, ElemType e, LNode **locate, int *i) {
    //定义指向当前结点的指针p,使其指向单链表L的首元结点
    LNode *p = L->next;
    //定义计数器j记录结点序号,初始值为1
    int j = 1;
    //循环改变指针p的指向并修改计数器j的值直至p=NULL(表示当前结点序号n+1,n时单链表L的当前长度,即不存在数据域和e匹配的结点,查找失败),或者当前结点数据域与e匹配,表示查找成功
    while (p && !match(p->data, e)) {
        p = p->next;
        ++j;
    }
    //判断循环结束的原因,若p=NULL,则说明查找失败,设置匹配的结点序号为-1,返回ERROR,表示查找失败
    if (!p) {
        *i = -1;
        *locate = p;
        return ERROR;
    }
    // 否则查找成功,计数器j的值即结点序号,返回OK
    *i = j;
    *locate = p;
    p = NULL;
    return OK;
    //返回前置指针p指向NULL
}


/**
 * 插入:在单链表L上的结点序号为i-1和结点序号为i的结点之间插入一个数据域为e的新结点
 * 算法描述:
 * 1、定义指向当前结点的指针p,使其指向头结点
 * 2、定义计数器j记录指针p从头结点移动到结点i-1所需移动的次数,考虑到i的合法范围是从1到n+1的整数,n是单链表当前结点个数,故j的合法范围是1到n
 * 3、循环移动指针p以及修改计数器直至p=NULL(表示移动到了第n+1个结点,仍未找到第i-1个结点,i > n+1),或者j = i -1(表示移动到了第i-1个结点,可以进行插入操作了)
 * 4、判断i是否合法,若p=NULL,说明i > n+1,i非法,返回ERROR,若j > i-1说明没有移动到第i-1个结点(因为正常情况下移动到第i-1个结点,恰好需要移动i-1次,说明i < 1,i非法,返回ERROR)
 * 5、若i合法,进行插入操作
 * 6、新建一个结点并使用指针s指向新结点,置新结点的数据域为e
 * 7、置新结点的指针域指向单链表序号为i的结点(置新结点的指针域为指向结点i-1的当前结点指针p的指针域的指向的地址)
 * 8、置当前结点指针p指向的序号为i-1的结点的指针域为新结点的地址
 * 9、令指针p和指针s指向NULL
 * 10、返回OK
 * @param L 指向头结点指针的指针(实现动态内存的传递)
 * @param i 插入位置,整数,合法范围1到n+1,n为单链表的当前长度
 * @param e 插入的新结点的数据域
 * @return
 */
Status insertNode(LinkedList *L, int i, ElemType e) {
    //定义指向当前结点的指针p,使其指向头结点
    LNode *p = *L;
    //定义计数器j记录指针p从头结点移动到结点i-1所需移动的次数,初始值为零,考虑到i的合法范围是从1到n+1的整数,n是单链表当前结点个数,故j的合法范围是1到n
    int j = 0;
    //循环移动指针p以及修改计数器直至p=NULL(表示移动到了第n+1个结点,仍未找到第i-1个结点,i > n+1),或者j = i -1(表示移动到了第i-1个结点,可以进行插入操作了)
    while (p && j < i - 1) {
        p = p->next;
        ++j;
    }
    //判断i是否合法,若p=NULL,说明i > n+1,i非法,返回ERROR,若j > i-1说明没有移动到第i-1个结点(因为正常情况下移动到第i-1个结点,恰好需要移动i-1次,说明i < 1,i非法,返回ERROR)
    if (!p || j > i - 1) {
        return ERROR;
    }
    //若i合法,进行插入操作
    //新建一个结点并使用指针s指向新结点,置新结点的数据域为e
    LNode *s = (LNode *) malloc(sizeof(LNode));
    s->data = e;
    //置新结点的指针域指向单链表序号为i的结点(置新结点的指针域为指向结点i-1的当前结点指针p的指针域的指向的地址)
    s->next = p->next;
    //置当前结点指针p指向的序号为i-1的结点的指针域为新结点的地址
    p->next = s;
    //令指针p和指针s指向NULL
    s = NULL;
    p = NULL;
    //返回OK
    return OK;
}

/**
 * 删除:删除单链表上结点序号为i的结点,即删除单链表上序号i-1和i+1之间的结点(由于i值的不同,结点i-1和i+1可能不存在),i的合法范围是从1到n的整数,n为当前单链表的结点个数
 * 算法描述:
 * 1、定义指向当前结点的指针p,使其指向单链表的头结点
 * 2、定义计数器j记录指针p移动至i-1结点移动的次数,因此j的合法范围是从0到n-1的整数
 * 3、循环移动指针p和修改计数器直至p->next=NULL(表示当前结点无后需结点,无法删除下一个结点),或者j=i-1(表示已经移动到了序号为i-1的结点,可以进行删除操作了)
 * 4、判断i值是否合法,若p->next=NULL,当前指针p已经移动了单链表的最后一个结点,即无结点可删除,返回ERROR,若j>i-1表示未移动到i-1结点,但是循环终止了,i<1,i值非法,返回ERROR
 * 5、若i值合法,定义指向结点i的临时指针t,使其指向结点i,即指针p的指针域所指向的地址
 * 6、置指针p指向的序号为i-1的结点的指针域为结点i的指针域指向的地址,若结点i无直接后继(此时结点i的指针域指向NULL),结点i-1在删除结点i后即最后一个结点,指针域指向NULL依旧合理
 * 7、释放指针t指向的结点i所占用的内存单元
 * 8、使指针p、t指向NULL
 * 9、返回OK
 * @param L 指向单链表的头指针的指针L(实现动态内存的传递)
 * @param i 需删除的结点在单链表的结点序号i
 * @return 操作结果状态码
 */
Status deleteNode(LinkedList *L, int i) {
    //定义指向当前结点的指针p,使其指向单链表的头结点
    LNode *p = *L;
    //定义计数器j记录指针p移动至i-1结点移动的次数,j初始值为零,因此j的合法范围是从0到n-1的整数
    int j = 0;
    //循环移动指针p和修改计数器直至p->next=NULL(表示当前结点无后需结点,无法删除下一个结点),或者j=i-1(表示已经移动到了序号为i-1的结点,可以进行删除操作了)
    while (p->next && j < i - 1) {
        p = p->next;
        ++j;
    }
    //判断i值是否合法,若p->next=NULL,当前指针p已经移动了单链表的最后一个结点,即无结点可删除,返回ERROR,若j>i-1表示未移动到i-1结点,但是循环终止了,i<1,i值非法,返回ERROR
    if (!p->next || j > i - 1) {
        return ERROR;
    }
    //若i值合法,定义指向结点i的临时指针t,使其指向结点i,即指针p的指针域所指向的地址
    LNode *t = p->next;
    //置指针p指向的序号为i-1的结点的指针域为结点i的指针域指向的地址,若结点i无直接后继(此时结点i的指针域指向NULL),结点i-1在删除结点i后即最后一个结点,指针域指向NULL依旧合理
    p->next = t->next;
    //释放指针t指向的结点i所占用的内存单元
    free(t);
    //使指针p、t指向NULL
    p = NULL;
    t = NULL;
    //返回OK
    return OK;
}

测试单链表和单链表基本操作的功能

int main() {
    LinkedList L = NULL;
    if (initLinkedList(&L)) {
        //test initialize LinkedList
        printf("successfully initialized the LinkedList \n");
    }
    //test insert node into LinkedList
    insertNode(&L, 1, 2);
    printf("insert a element which index is 1 and data is 2 into the LinkedList\n");
    insertNode(&L, 2, 2.5);
    printf("insert a element which index is 2 and data is 2.5 into the LinkedList\n");
    double d;
    LNode *p = NULL;
    int i;
    //test find a element on LinkedList
    if (indexOf(L, 2, &p, &i)) {
        printf("now the index of element which data is 2 is %d\n", i);
        //test get a element's data from LinkedList by index
        getElem(L, i, &d);
        printf("now the data of the element which index is %d is %lf\n", i, d);
    } else {
        printf("index = %d\n", i);
    }
    //test delete a element from LinkedList
    printf("now delete a element which index is 1 from the LinkedList\n");
    deleteNode(&L, 1);
    if (indexOf(L, 2.5, &p, &i)) {
        printf("inow the index of element which data is 2.5 is %d\n", i);
        printf("now the data of the element which address is %d is %lf\n", p, d);
    } else {
        printf("index = %d\n", i);
    }
    return 0;
}

为了结果清晰明了,可删除printf函数内的英文

运行结果:

猜你喜欢

转载自www.cnblogs.com/RGBTH/p/13365519.html