上一篇我们学习了线性表,但是线性表有一个缺点,就是表满了之后就不能再插入了,所以就有了今天的链表(link list)。
链表又分好几种:
- 单向链表(single link list)
- 循环链表(circular link list)
- 双向链表(double link list)
- 静态链表(static link list)
今天我们先了解下最简单的单项链表。 单项链表github源码链接
定义数据结构
typedef int ElementType;
typedef struct stNode
{
ElementType data;
struct stNode *next;
}stNode;
typedef struct stNode* pstLinkList;
定义接口
BOOL InitList(pstLinkList* pstll, int n);
BOOL IsListEmpty(pstLinkList pstll);
BOOL GetptrList(pstLinkList pstll, int index, pstLinkList* pll);
BOOL GetElem(pstLinkList pstll, int index, ElementType * elem);
BOOL GetIndex(pstLinkList pstll, ElementType elem, int* index);
void GetLength(pstLinkList pstll, int* len);
BOOL InsertElem(pstLinkList pstll, int index, ElementType elem);
BOOL PushElem(pstLinkList pstll, ElementType elem);
BOOL DeletElem(pstLinkList pstll, int index);
BOOL PopElem(pstLinkList pstll);
void ClearList(pstLinkList pstll);
void PrintList(pstLinkList pstll);
具体实现说几个比较重要的函数。
static BOOL CreatNode(pstLinkList* p) /* double ** */
{
*p = (pstLinkList)malloc(sizeof(stNode));
if(!(*p))
return FALSE;
(*p)->next = NULL;
(*p)->data = 666;
return TRUE;
}
这个是为了插入元素时创建一个节点用的。值得注意的是我们需要对节点的地址操作,而不是对节点的拷贝操作。也就是说这个函数的input只能是 pstLinkList* 类型,而不能是pstLinkList类型,否则只是对pstLinkList的一个对象的拷贝进行操作,并不能对我们想要的节点进行内存申请操作。
初始化一个表。
/*
* creat head point to first node
*/
BOOL InitList(pstLinkList* pstll, int n) /* double ** */
{
int i;
pstLinkList pstnode,p;
CreatNode(pstll);//creat head pointer
p = *pstll;
for(i=0; i<n; i++)
{
CreatNode(&pstnode);
pstnode->data = i;
pstnode->next = p->next;
p->next = pstnode;
}
return TRUE;
}
初始化一个表的时候和CreatNode函数都是需要我们提供节点的地址pstLinkList*, 而不是pstLinkList。
博主为了方便插入和删除元素,另外编写了一个查找某一个index下的节点地址的函数。
/*
* if index=0 ,return the head pointer
* pll <----> index =0~len
*/
BOOL GetptrList(pstLinkList pstll, int index, pstLinkList* pll)
{
int count;
pstLinkList p;
p = pstll;//head pointer
count = 0;
while(p && index>=0)
{
if(index == count)
{
*pll = p;
return TRUE;
}
else
{
p = p->next;
count++;
}
}
*pll = NULL;
return FALSE;
}
接下来就是最关键的插入删除表了。
/*
* insert node at index
*/
BOOL InsertElem(pstLinkList pstll, int index, ElementType elem)
{
pstLinkList p,pNode;
/*index>=1 && index <= len+1*/
if(GetptrList(pstll, index-1, &p))
{
CreatNode(&pNode);
pNode->data = elem;
pNode->next = p->next;
p->next = pNode;
return TRUE;
}
return FALSE;
}
BOOL DeletElem(pstLinkList pstll, int index)
{
pstLinkList p,pDelNode;
if(GetptrList(pstll, index-1, &p))
{
pDelNode = p->next;
if(!pDelNode)//in case p is the end
return FALSE;
p->next = pDelNode->next;
free(pDelNode);
printf("free ok!\n");
return TRUE;
}
return FALSE;
}
我们同样实现了PushElem、PopElem类似栈的操作,即在表尾插入或者删除一个元素。
BOOL PushElem(pstLinkList pstll, ElementType elem)
{
int len;
GetLength(pstll, &len);
return InsertElem(pstll,len+1,elem);
}
BOOL PopElem(pstLinkList pstll)
{
int len;
GetLength(pstll, &len);
return DeletElem(pstll,len);
}
最后就是销毁一个表,这里尤其要注意,否则容易出现内存溢出(core dumped)错误。
也就是我们malloc了多少次,最终也要free掉多少次。
我的调试方法是设置一个全局变量malloc_cnt,在每一个内存分配的地方打印这个数据
printf("malloc count: %d\n", malloc_cnt++);
同样的,在没一个free的地方搞一个free_cnt,
printf("free count: %d\n", free_cnt++);
最后比较这两个数值就知道那个地方内存出错啦~
最后测试下我们的程序
int main(int argc, char const *argv[])
{
pstLinkList pstll, pll;
ElementType elem;
int index,len;
if(!InitList(&pstll, 5))
{
printf("Init failed!\n");
ClearList(pstll);
return 0;
}
if(IsListEmpty(pstll))
{
printf("this link list is empty!\n");
ClearList(pstll);
return 0;
}
printf("link list elements are:\n");
printf("======\n");
PrintList(pstll);
printf("\n\n\n");
GetLength(pstll, &len);
printf("link list length: %d\n", len);
if(GetIndex(pstll, 3, &index))
printf("elem 3 at the index: %d\n",index);
else
printf("elem 3 is not existing in the link list\n");
if(GetElem(pstll, 4, &elem))
printf("elem at the index 4: %d\n", elem);
else
printf("index is not elligal!\n");
printf("\n\n\n");
printf("insert elem 90 at index 3: \n");
InsertElem(pstll, 3, 90);
printf("now, link list's elem are: \n");
PrintList(pstll);
printf("\n\n\n");
printf("push elem 88 at the end of the link list: \n");
PushElem(pstll, 88);
printf("now, link list's elem are: \n");
PrintList(pstll);
printf("\n\n\n");
printf("delet the elem at the index 1: \n");
DeletElem(pstll, 1);
printf("now, link list's elem are: \n");
PrintList(pstll);
printf("\n\n\n");
printf("pop elem at the the end of the link list\n");
PopElem(pstll);
printf("now, link list's elem are: \n");
PrintList(pstll);
printf("\n\n\n");
printf("clear the list...\n");
ClearList(pstll);
printf("link list datastructure exercise is the end...\n");
printf("see u ^_^ !\n");
printf("\n\n\n");
return 0;
}