链表与数组
在编程语言中,数组数据结构(array data structure),简称数组(Array),是一种数据结构,是数据元素(elements)的集合。有限个相同类型的元素按顺序存储,用一个名字命名,然后用编号区分他们的变量的集合;这个名字称为数组名,编号称为下标。
链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是不同与数组的是,并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序存储。
一、链表与数组的优缺点
// 数组的缺点:
//
// 1.一旦数组定义,则大小固定,无法进行修改(数组的大小)。
// 2.数组插入和删除的效率太低,时间复杂度O(n)。
//
// 数组的优点:
//
// 1.下标访问,速度快,时间复杂度是O(1)
//
//////////////////////////////////////////////////////////
//
// 链表:
//
// 链表的优点:
//
// 1.资源允许的情况下,规模可以不断的增长或减小。
// 2.删除和添加效率高,O(1)
//
// 链表的缺点:
//
// 链表的遍历过程效率比较低。
二、数据结构类型的定义
//方法1
int array1[10] = {0};
//方法2
int array2[] = {12, 23, 34, 45, 56, 67, 78};
这里,我们的array1和array2都在定义时直接分配了 <栈> 上的内存空间
//方法3
int *array3 = NULL;
array3 = (int *)malloc(sizeof(int) * 100);
if(array3 != NULL){
fprintf(stderr, "the memory is full!\n");
exit(1);
}
array3是一个int类型的指针,指向我们在 <堆> 上用malloc分配的4bytes ×100 = 400 bytes大小的空间。
我们的数组虽然在O(1)的时间复杂度访问下标进行数据存取查找,可是一般数组定义后,大小不能够进行动态变化,且插入删除效率过低,时间复杂度为O(n).
所以,为了弥补这些不足,我们又学习到了链表。
链表的定义
链表是一种物理存储单元上非连续、非顺序的存储结构,链表由相同结构的链表节点构成,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。可以关注链表节点的结构:
typedef struct List_node
{
int data;
struct List_node *next;
}List_node;
如何把两个节点进行连接:
这个指针指向类型为链表节点,这样每一个节点就可以记录下下一个节点的位置,从而把原本毫不相干的链表节点串接起来。
一个链表节点定义;
typedef struct P_Node{
//数据域
char name[20]; //姓名
char sex; //m 男 w 女 性别
char age; //年龄
char jobs[20]; //职业
//指针域
struct P_Node *next;
}P_Node;
//一个职工的结构体
再申请一个链表节点;
P_Node node1 = {0};
P_Node *p_node = (Node *)malloc(sizeof(Node));
node1.next = p_node;
三、链表的常见接口
我们把可以对链表的操作是在.h文件中进行声明的,我们叫这样的声明为接口,如下是单链表的接口声明:
/*list.h
*
* 带头节点的链表
*
*
*/
#ifndef _LIST_H
#define _LIST_H
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#endif
#define TRUE (1)
#define FALSE (0)
typedef unsigned char Boolean;
//定义链表节点类型
typedef struct List_node{
int data;
struct List_node *next;
}List_node;
typedef List_node *List_head;
/*
*链表的接口
*---------------------------------------------------------
* 常见接口:
* 1.初始化
* 2.销毁
* 3.增加
* 4.删除
* 5.查找
* 6.修改
* 7.排序
* 8.显示
*----------------------------------------------------------
*/
List_head init_list(void) ;
void destroy_list(List_head *head) ;
Boolean push_front(List_head head, int value) ;
Boolean push_back(List_head head, int value) ;
Boolean pop_front(List_head head) ;
Boolean pop_back(List_head head) ;
Boolean find_node(List_head head, int value, List_node **node) ;
void modify_node(List_node * node, int value) ;
Boolean insert_node(List_head head, int index, int value) ;
void sort_list_ascend(List_head head) ;
void sort_list_descend(List_head head) ;
void show_list(List_head head) ;
int get_list_length(List_head head) ;
我们可以通过定义 包裹函数 来缩短程序。每个包裹函数完成实际的函数调用,检查返回值,并在发生错误时终止进程。
包裹函数其实就是封装函数,调用一个函数来实现这个功能。既然我们会经常用到一些函数并且还要进行错误处理,那么,我们可以将这些函数封装起来,也放入头文件.h当中。
//包裹函数
#ifndef _TOOL_H_
#define _TOOL_H_
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#endif
static List_node *create_node(void);
static void *Malloc(size_t size);
static void swap(void *a, void *b, int length);
static List_node *create_node(void)
{
//申请节点,并且对节点初始化(置为0)
List_node *node = (List_node *)Malloc(sizeof(List_node));
bzero(node, sizeof(List_node));
return node;
}
static void *Malloc(size_t size)
{
void *result = malloc(size);
if(result == NULL){ //失败情况处理
fprintf(stderr, "the memory is full!\n");
exit(1);
}
return result;
}
static void swap(void *a, void *b, int length)
{
void *temp = Malloc(length);
memcpy(temp, a, length);
memcpy(a, b, length);
memcpy(b, temp, length);
free(temp);
}
四、链表接口的实现
为了能够使用这些我们所提供的接口,我们要能够实现这些操作,可以在list.c中进行实现。
//1初始化 List_head <==>List_node *
List_head init_list(void)
{
List_head head = NULL;
head = create_node();
return head;
}
//2 销毁
void destroy_list(List_head *head)
{
List_node *p = NULL;
List_node *q = NULL;
if(head == NULL || *head == NULL){
return ;
}
p = *head;
//从头到尾一次销毁知道最后一个节点
while(p != NULL){
q = p;
p = p->next;
free(q);
}
//防止野指针
*head = NULL;
}
//3 头部插入
Boolean push_front(List_head head, int value)
{
List_node *node = NULL;
if(head == NULL){
return FALSE;
}
node = head->next;
head->next = create_node();
head->next->data = value;
head->next->next = node;
/*
node = create_node();
node->data = value;
node->next = head->next;
head->next = node;
*/
head->data++;
return TRUE;
}
//尾部插入
Boolean push_back(List_head head, int value)
{
List_node *node = NULL;
if(head == NULL){
return FALSE;
}
node = head;
//寻找尾节点
while(node->next != NULL){
node = node->next;
}
//插入新节点
node->next = create_node();
node->next->data = value;
head->data++;
return TRUE;
}
//头部删除节点
Boolean pop_front(List_head head)
{
List_node * p = NULL;
//判断链表是否存在或者是否有有效节点
if(head == NULL ||head->next == NULL){
return FALSE;
}
p = head->next;
head->next = p->next;
free(p);
head->data--;
return TRUE;
}
//尾部删除节点
Boolean pop_back(List_head head)
{
List_node *p = NULL;
//判断链表是否存在或者是否有有效节点
if(head == NULL ||head->next == NULL){
return FALSE;
}
p = head;
//找到倒数第二个结点
while(p->next->next != NULL){
p = p->next;
}
free(p->next);
p->next = NULL;
head->data--;
return TRUE;
}
//寻找指定节点
Boolean find_node(List_head head, int value, List_node **node)
{
List_node *p = NULL;
if(head == NULL){
return FALSE;
}
p = head->next;
while(p != NULL){
//寻找匹配的节点
if(p->data == value){
//找到而且*node节点不等于NULL,将p存入*node
if(*node != NULL){
*node = p;
}//end if
return TRUE;
}//end if
p = p->next;
}
return FALSE;
}
//修改某节点
void modify_node(List_node * node, int value)
{
node->data = value;
}
//在指定节点后插入一个节点
Boolean insert_node(List_head head, int index, int value)
{
List_node *node = NULL;
List_node *p_node = NULL;
int cout = index;
//链表不存在或者插入不合法
if(head == NULL || index < 0 || index > head->data){
return FALSE;
}
//寻合适位置,被插入节点的前一个位置
node = head;
while(cout--){
node = node->next;
}
//p_node记录下要插入节点的下一个位置
p_node = node->next;
//创建新节点并插入
node->next = create_node();
node->next->data = value;
node->next->next = p_node;
head->data++;
return TRUE;
}
//升序排序
void sort_list_ascend(List_head head)
{
if(head == NULL || head->data < 2){
return ;
}
List_node *p = NULL;
List_node *q = NULL;
/////////////////////////////////////////////////////////////////
p = head->next;
while(p->next != NULL){
q = p->next;
while(q != NULL){
if(p->data > q->data){
swap(p,q,(unsigned long)(&((List_node *)0)->next));
}
q = q->next;
}
p =p->next;
}
////////////////////////////////////////////////////////////////
/*
for(p = head->next; p->next; p = p->next){
for(q = p->next; q; q = q->next){
if(p->data > q->data){
swap(p,q,(unsigned long)(&((List_node *)0)->next));
}
}*/
}
//降序
void sort_list_descend(List_head head)
{
if(head == NULL || head->data < 2){
return ;
}
List_node *p = NULL;
List_node *q = NULL;
/////////////////////////////////////////////////////////////////
p = head->next;
while(p->next != NULL){
q = p->next;
while(q != NULL){
if(p->data < q->data){
swap(p,q,(unsigned long) (&((List_node *)0)->next));
}
q = q->next;
}
p =p->next;
}
////////////////////////////////////////////////////////////////
/* for(p = head->next; p->next; p = p->next){
* for(q = p->next; q; q = q->next){
if(p->data < q->data){
swap(p,q,(unsigned long) (&((List_node *)0)->next));
}
* }
* }
*/
}
//打印
void show_list(List_head head)
{
if(head == NULL){
return ;
}
List_node *node = NULL;
node = head->next;
while(node != NULL){
printf("%d ",node->data);
node = node->next;
}
printf("\n");
}
//获取链表长度
int get_list_length(List_head head)
{
if(head == NULL){
return -1;
}
return head->data;
}
设计完这个单链表的接口与实现之后,我们要对相应功能进行检测。
测试代码如下:
int main(int ac, char **av)
{
int i = 0;
int seed = 10;
List_head head = NULL;
head = init_list();
List_node *node = create_node();
printf("\n头部插入:\n");
for(i = 0 ; i < seed ; i++ )
{
push_front(head,rand()%100);
}
show_list(head);
printf("\n尾部插入:\n");
for(i = 0 ; i < seed ; i++ )
{
push_back(head,rand()%100);
}
show_list(head);
printf("\n头部删除:\n");
pop_front(head);
show_list(head);
printf("\n尾部删除:\n");
pop_back(head);
show_list(head);
printf("\n插入节点:\n");
insert_node(head, 3, 100);
show_list(head);
printf("\n查找节点值为77的节点: ");
i = find_node(head, 77,&node);
if(i == TRUE){
printf("找到了\n");
}els{
printf("没找到\n");
}
printf("\n升序排序:\n");
sort_list_ascend(head);
show_list(head);
printf("\n降序排序:\n");
sort_list_descend(head);
show_list(head);
printf("\n修改节点数据:\n");
node = head->next->next;
modify_node(node, 777);
show_list(head);
printf("\nthe length of this list :%d\n",get_list_length(head));
destroy_list(&head);
return 0;
}
运行结果:
[root@linux-daemon 1]# gcc main.c list.c -o main
[root@linux-daemon 1]# ./main
头部插入:
21 49 92 86 35 93 15 77 86 83
尾部插入:
21 49 92 86 35 93 15 77 86 83 62 27 90 59 63 26 40 26 72 36
头部删除:
49 92 86 35 93 15 77 86 83 62 27 90 59 63 26 40 26 72 36
尾部删除:
49 92 86 35 93 15 77 86 83 62 27 90 59 63 26 40 26 72
插入节点:
49 92 86 100 35 93 15 77 86 83 62 27 90 59 63 26 40 26 72
查找节点值为77的节点: find
升序排序:
15 26 26 27 35 40 49 59 62 63 72 77 83 86 86 90 92 93 100
降序排序:
100 93 92 90 86 86 83 77 72 63 62 59 49 40 35 27 26 26 15
修改节点数据:
100 777 92 90 86 86 83 77 72 63 62 59 49 40 35 27 26 26 15
the length of this list :19
完。