单链表实现
链表特点:
- 链表是指一个数据元素含有一个或多个指向另外一个数据元素的指针或引用。称这样的元素为节点。一条链上的节点,多形象。
链表结构的元素在内存中是分散存放的。不需要连续的存储空间。空间利用率较高。可以动态增加长度。 - 而由于元素是通过指针联系在一起的,所以插入和删除元素较快,只需要修改插入和删除位置元素的指向即可。这些是链表的优势。
- 链表的劣势是对元素的访问只能遍历寻找(不包含特殊指向如头尾)。查找和访问元素较慢。下面看一组对比:
数组和链表对比
操作 | 数组 | 链表 |
---|---|---|
按位置读 | 下标访问O(1) | 遍历O(n) |
元素定位 | O(n) | O(n) |
插入元素 | O(n) | O(n) |
删除 | O(n) | O(n) |
元素数量限制 | 初始化参数 | 内存容量 |
数据冗余 | 无 | O(n) |
注: 定位、插入和删除两种操作之所以复杂度都是O(n)是因为都涉及到了遍历元素,在涉及到所有元素的算法里复杂度最少是O(n)因为至少要读一遍元素。
虽然两种结构插入删除近似时间复杂度O相同,但是我觉的链表的效率是比较高的,分析如下:
一、 数组和链表的遍历是不同的,数组是要遍历移动元素(需要读写内存读i-1存到i)以挪出空位或填补空位,而链表是O(n)的读内存操作(定位元素), O(1)更改指针操作。
所以对内存的操作少一半。
二、对于双链表删除,当我们持有待删除元素指针时就可以直接操作待删除元素指向来删除元素.这时删除操作的时间复杂度是O(n).插入元素也大同小异.
另外链表的遍历也有很多优化时间性能的方法.
demo代码如下:
注:代码分析在最后,修改于2017/6/9.
//定义node
typedef struct person2{
char name[10];
int age;
struct person2 *next;
}person2;
//用于封装链表
typedef struct linkedList{
person2 *head;
person2 *end;
int length;
}linkedList;
//初始化node
person2 *newP2(char *name,int age){
//动态分配内存
person2 *p = (person2 *)malloc(sizeof(person2));
p->age = age;
strcpy(p->name,name);
p->next = NULL;
return p;
}
//建立空表
linkedList *newList(){
//建立头结点
linkedList *head = (linkedList *)malloc(sizeof(linkedList));
//头结点分配实体
head->head = newP2("",-1);
//尾节点指向头结点
head->end = head->head;
head->length = 0;
return head;
}
//add element in end
void addEnd(linkedList *head, person2 *p){
//使链表的最后一个节点(end)的next指针指向新节点。
head->end->next = p;
//更新尾节点
head->end = p;
head->length++;
}
//根据index序号读表元素,返回数据域data(index序号按写入顺序,从0开始)
person2 *readL(linkedList *head, int index){
if(head->length > index && index >= 0 ){
person2 *phead = head->head->next;
int j = 0;
while (phead != NULL && j < index){
phead = phead->next;
j++;
}
if(j == index){
return phead;
}
}
return NULL;
}
//定位元素 返回下标(从0开始)
int locatep2(linkedList *head, person2 *p){
int i;
person2 *phead = head->head->next;
for(i = 0; phead != NULL; i++){
if(compareP2(phead,p)){
return i;
}
phead = phead->next;
}
return -1;
}
//person 比较器
int compareP2(person2 *p1,person2 *p2){
if(p1 == p2){
return 1;
}
return (p1->age == p2->age && !strcmp(p1->name,p2->name));
}
//insert element before index
void insertlink(linkedList *head,person2 *per,int index){
person2 *p;
//得到要插入位置的前一个元素
if(index == 0){
p = head->head;
}else if(index == head->length){
addEnd(head,per);
return ;
}else{
p = readL(head,index-1);
}
//insert element
if(p != NULL){
per->next = p->next;
p->next = per;
head->length++;
}else{
printf("index not find");
exit(1);
}
}
//delete element by index
void myRemove2(linkedList *head,int index){
person2 *p1;
//如果要删除第一个元素
if(index == 0 ){
//指向第一个节点的head
person2 *phead = head->head;
//待删除元素p1指向首节点
p1 = phead->next;
//使head指向下下一个节点
phead->next = phead->next->next;
}
else if(index == head->length-1){
p1 = head->end;
head->end = readL(head,index);
}
else{ //不是首尾节点的情况
//得到要插入位置的节点
person2 *p3 = readL(head,index-1);
//delete element
if(p3 == NULL){
printf("index not find");
exit(1);
}
p1 = p3->next;
p3->next = p1->next;
}
free(p1);//一定要释放节点空间
head->length--;
}
//print element
void linkPrint(linkedList *head){
int length = head->length;
person2 *begin = head->head->next;
printf("***********length = : %d ************\n",length);
for(;begin != NULL; begin = begin->next){
printf("name: %s,age: %d\n",begin->name,begin->age);
}
}
void linkTest(){
person2 *p1;
//printf("hello world\n");
//初始化
linkedList *head = newList();
addEnd(head,newP2("HH",0));
addEnd(head,newP2("HH",1));
addEnd(head,newP2("HH",2));
addEnd(head,newP2("HH",3));
addEnd(head,newP2("HH",4));
addEnd(head,newP2("HH",5));
linkPrint(head);
//测试按下标查找
p1 = readL(head,2);
if(p1 != NULL)
printf("found person 2 :name = %s,age = %d\n",p1->name,p1->age);
//test 按元素查找位置
printf("found person index: index = %d\n",locatep2(head,newP2("HH",2)));
//test insert element
printf("insert element 0 ,4\n");
insertlink(head,newP2("new",66),0);
insertlink(head,newP2("new",66),4);
linkPrint(head);
//test delete element
printf("delete element 0 ,4\n");
myRemove2(head, 0);
myRemove2(head, 4);
linkPrint(head);
}
结构体分析
对于节点,我们只需让其携带必要的数据即可,上面的例子中node结构代码如下:
struct person{
dataType data;
person *next;
};
但是这样写有一个问题,怎么表示head、end、length。或者在哪里封装?办法是建一个专门的头结点结构如下:
struct linkList{
person *head;//head->next指向链表第一个元素
person *end;//指向链表最后一个元素
int length;//记录表长
};
**对于封装头结点结构体,每个链表只需要一个**,
这样封装头结点可以实现addFirst 和 addLast,这需要在建空表的时候创建两个实体person节点来表示队首和队尾,这样每条链表就有两个节点冗余,但还能接受。
本例只创建了一个head实体,来实现在队尾添加节点。
改进版本的实现见下一篇 双向链表。