C语言程序开发宝典-数据结构

C语言数据结构精讲
本文深入探讨C语言中的数据结构实现,包括链表、栈、队列等基本概念及应用,通过具体实例讲解结构体定义、链表操作、循环队列及串处理等关键知识点。

2017/06/18 10:35
好多好多,学习 真非一朝一夕之功,贵在坚持,在工作即将满2年的时候,还在恶补C语言,真是罪过罪过...
数据结构、算法、文件操作、库函数应用、系统、加密解密、网络通信还有这么多东西要看,看着就头大。。。
今天来看:结构体:
实例073:定义一个结构体

struct student
{
    int num;
    char name[20];
    float score;
};

定义结构体数组:

struct student stu[5] = 
    {
        {
            101, "liming", 89
        } , 
        {
            102, "zhanghong", 95
        }
        , 
        {
            103, "lili", 89
        }
        , 
        {
            104, "weichen", 85
        }
        , 
        {
            105, "yangfan", 75
        }
    };

实例075:比较计数

实例076:信息查询
strcmp(b[i].name,x)相等时strcmp返回0;

实例077:计算开机时间,程序报错:
fread.c读取出错

网上找到调用time.h打印当前时间的方法

#include<time.h>
#include<string.h>
#include<stdio.h>
#include <conio.h>
void main()
{
char Now_time[30];
//char *t;
//t=Now_time;
//strcpy(Now_time,_strtime(t));   
printf("%s",_strtime(Now_time));
_getch();
}

实例078:创建单向链表
链表包含头指针、头结点(非必须),定义链表

struct LNode
{
    int data;
    struct LNode *next;
};

生成一个不含头结点的链表:尾插法,传入链表长度,传出头指针;

struct LNode *create(int n)
{
    int i;
    struct LNode *head,  *p1,  *p2;
    int a;
    head = NULL;
    printf("Input the integers:\n");
    for (i = n; i > 0; --i)
    {
        p1 = (struct LNode*)malloc(sizeof(struct LNode)); /*分配空间*/
        scanf("%d", &a);                /*输入数据*/
        p1->data = a; /*数据域赋值*/
        if (head == NULL)  /*指定头结点*/
        {
            head = p1;
            p2 = p1;
            /*printf("头指针和p2都指向新生成的指针p1");
            printf("head->data=%d\n",head->data);
            printf("p2->data=%d\n",p2->data);
            printf("p1->data=%d\n",p1->data);*/
        } 
        else
        {
            p2->next = p1; /*指定后继指针*/
            p2 = p1;
            /*printf("p2->data=%d\n",p2->data);
            printf("p1->data=%d\n",p1->data);*/
        }
    }
    p2->next = NULL;
    return head;
}

需注意当头指针为空时,将head和p2指针都指向p1;

此创建链表的方式为在一个子函数内创建;

若生成一个含头结点的链表

typedef struct LNode Linklist;
首先创建一个头结点,数据域一般为空,暂时就写个空字吧,

Linklist *create()
{
    Linklist *head;
    head=(Linklist*)malloc(sizeof(Linklist));
    head->data='空';
    head->next=NULL;
    return head;
}

然后头插法建立链表:

Linklist *head_insert(Linklist *head,int value)
{
    Linklist *p,*t;
    t=head;
    p=(Linklist *)malloc(sizeof(Linklist));
    p->data=value;
    p->next=t->next;
    t->next=p;//改变t->next指针的指向;
    return head;

}

头插法每次将新生成的节点插入到表头;

尾插发建立链表:

Linklist *tail_insert(Linklist *head,int value)
{
    Linklist *p,*t;
    t=head;
    p=(Linklist *)malloc(sizeof(Linklist));
    p->data=value;
    while(t->next!=NULL)
        t=t->next;
    t->next=p;
    p->next=NULL;
    return head;
}

尾插法插入一个元素,效率有点低啊,每次插入一个元素,都有移动指针到末尾。

注:2017-06-27,修改,上面创建链表的方法有问题,应该用指向指针的指针而不是指针。
区别:从别处粘过来直接copy,
使用指针的地址做链表创建的输入

附:创建链表的正确方法:
以下一段引用自:《C语言–凌阳教育嵌入式–C语言》

#include <stdio.h>
#include <stdlib.h>
#include<string.h>
#include<conio.h>
typedef struct student
{                   //数据域
    int num;        //学号
    int score;  //分数
    char name[20];
    struct student *next;//指针域
}STU;

void link_creat_head(STU **p_head,STU *p_new)
{
    STU *p_mov=*p_head;
    if(*p_head==NULL)               //当第一次加入链表为空时,head执行p_new
    {
        *p_head=p_new;
        p_new->next=NULL;
    }
    else    //第二次及以后加入链表
    {       
        while(p_mov->next!=NULL)
        {
            p_mov=p_mov->next;  //找到原有链表的最后一个节点
        }   
        p_mov->next=p_new;      //将新申请的节点加入链表
        p_new->next=NULL;
    }
}

void link_creat_end(STU **p_head,STU *p_new)//头插法
{
    STU *p_mov=*p_head;
    if(*p_head==NULL)   //当第一次加入链表为空时,head执行p_new
    {
        *p_head=p_new;
        p_new->next=NULL;
    }
    else    //第二次及以后加入链表
    {       
        p_new->next = *p_head;
        *p_head=p_new;
    }
}
注:头插法需注意顺序是反的。

void link_print(STU *head)
{
    STU* p_mov=head;
    if(head==NULL)
        printf("链表为空\n");
    else
    {
        while(p_mov!=NULL)
        {
            printf("%d %d %s\n",p_mov->num,p_mov->score,p_mov->name);
            p_mov=p_mov->next;
        }
    }
}

int main()
{
    STU *head=NULL,*p_new=NULL;
    int num,i;
    printf("请输入链表初始个数:\n");
    scanf("%d",&num);
    for(i=0;i<num;i++)
    {   
        p_new=(STU*)malloc(sizeof(STU));//申请一个新节点
        printf("请输入学号、分数、名字:\n");   //给新节点赋值
        scanf("%d %d %s",&p_new->num,&p_new->score,p_new->name);
        link_creat_end(&head,p_new);    //将新节点加入链表
    }
    link_print(head);
    _getch();
    return 0;

}

这就是传指针和传“指针的指针”的区别:
1.传指针:我们是拷贝一份地址传过去,那么对于该地址块上值的修改则会影响实参(即main()函数中的clink *h的值),但仅限于对该地址块,所以如果实参指针为空,而你在函数createclink中重新申请空间,注意这时候地址空间变了,就像你main()函数中的h依旧为空,因为函数createclink中没有对实参h的值做改变,因此,你的createclink函数没起到作用.
2.传指针的指针:这和传指针的引用效果一样,这时候值的改变中的“值”就是h,即一个地址(而上面的“传指针的值”是“该指针指向的值”),因此在createclink函数中申请空间,main()函数中h也有了新的空间,因为这种传值改变的是“地址”。

实例080:创建双向链表:
双向链表的定义为:
一个指针指向前驱,一个指针指向后继;

typedef struct node
{
    char name[20];
    struct node *prior,*next;
}stud;

以下为实例080稍作修改,原例子中未考虑查找元素不存在的情况;

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <string.h>
typedef struct node
{
    char name[20];
    struct node *prior,*next;
}stud;

stud *create(int n)//创建双链表
{
    stud *p,*h,*s;
    int i;
    h=(stud*)malloc(sizeof(stud));//注:这地方直接生成了头结点,与实例078不同,
    h->name[0]='\0';
    h->prior=NULL;
    h->next=NULL;
    p=h;
    for(i=0;i<n;i++){
        s=(stud*)malloc(sizeof(stud));
        p->next=s;
        printf("Input the %d student's: ",i+1);
        scanf("%s",&(s->name));
        s->prior=p;
        s->next=NULL;
        p=s;
    }
    p->next=NULL;
    return (h);
}

stud  *search(stud *h,char *x)//查找为x的字符,并返回链表位置
{
    stud *p;
    char *y;
    p=h->next;
    while(p)
    {
        y=p->name;
        if (strcmp(y,x)==0){
          return (p);
        }
        else{
          p=p->next;
        }

    }
    printf("cannot find data!\n");
    return 0;
}

void del (stud *p)   //删除链表的一个结点
{
    p->next->prior=p->prior;//指针p的前驱
    p->prior->next=p->next;//指针p的后继
    free(p);
}

void main()
{
    int number;
    char sname[20];
    stud *head,*sp;
    puts("Please input the size of the list: ");
    scanf("%d",&number);
    head=create(number);
    sp=head->next;
    printf("\nNow the double list is:\n");
    while(sp)
    {
        printf("%s ",sp->name);
        sp = sp->next;
    }
    printf("\n Please input the name which you want to find:\n");
    scanf("%s",sname);
    sp=search(head,sname);
    if (sp!=0){
        printf("the name you want to find is :%s\n",sp->name);
        /*printf("zhi=%d\n",&sp);*/
        printf("zhi1=%x\n",sp);//为sp的地址

        del(sp);
        sp=head->next;
        printf("\n Now the double list is:\n");
        while(sp){
            printf("%s ",sp->name);
            sp=sp->next;
        }
        printf("\n");
        puts("\n Press any key to quit...");
    }
    else{

    printf("什么都没有");
    printf("zhi1=%d\n",sp);
    }
    _getch();
}

调试运行结果如下:
实例080查找并删除链表结点

注:上述查找链表:返回写的不好:以下做的比较好:
查找时,返回在最后,若是查找不到,返回的是NULL指针。

STU* search_name_link(STU *p_mov,char *name)
{
    while(p_mov!=NULL)
    {
        if(strcmp(name,p_mov->name)==0)
            break;
        p_mov=p_mov->next;
    }
    return p_mov;
}

释放一个链表:使用指向指针的指针

void link_free(STU**p_head)
{
    STU *temp;
    while(*p_head!=NULL)
    {
        temp=(*p_head)->next;
        free(*p_head);
        *p_head=temp;
    }
}

删除链表中的一个结点:

void link_delete_name(STU **p_head,char *name)
{
    STU * pf,*pb;
    pb=pf=*p_head;
    if(*p_head==NULL)// 如果链表为空 返回NULL不需要删
    {
        printf("link is NULL\n");
        return ;
    }
    while((strcmp(pb->name,name)!=0) && (pb->next!=NULL))//循环找pb的num为num的节点
    {
            pf=pb;//pf记录一下pb的位置
            pb=pb->next;//pb指向下一个节点
    }
    if(strcmp(pb->name,name)==0)//找到了要删除的节点
    {
        if(pb==*p_head)//要删除的节点是头节点
        {
            *p_head=(*p_head)->next;
        }
        else//要删除的节点是普通节点
        {
            pf->next=pb->next;// pf 指向pb的下一个节点
        }
        free(pb);
    }
    else//没有节点的num为num
    {
        printf("没有您要删除的节点\n");
    }
}

summary:除了上述链表创建、打印、查找、删除外还有:
链表的插入、排序、逆序。。。

2017/06/29 11:00
根据链表结果中的num位置插入节点到链表中:

void link_insert(STU **p_head,STU *p_new)
{
    STU * pf,*pb;
    pb=pf=*p_head;
    if(*p_head==NULL)// 如果链表为空,则pi即为head
    {
        *p_head=p_new;
        return ;
    }

    while((p_new->num >= pb->num) && (pb->next!=NULL))//循环找pb的num为num的节点
    {
            pf=pb;//pf记录一下pb的位置
            pb=pb->next;//pb指向下一个节点
    }
    if(p_new->num < pb->num)//找到pb的num比p_new的num大了,p_new 插在pb的前边
    {
        if(pb==*p_head)//插在第一个节点的前边
        {
            p_new->next=*p_head;//新来的节点指向 原先的head
            *p_head=p_new;//p_new 指向的节点 变成头节点
        }
        else//插在普通节点的前边
        {
            pf->next=p_new;
            p_new->next=pb;
        }
    }
    else//没有找到pb的num比pi的num大,插在链表的尾端 
    {
        pb->next=p_new;
        p_new->next=NULL;//p_new 做为尾节点
    }
}

链表的排序:
相当于两重for循环遍历链表中的数值,然后按照顺序排序

STU * order(STU * head)//链表的排序
{
    STU temp;
    STU *pf,*pb;
    pb = pf = head;
    if(head == NULL)        
    {
        printf("list is null\n");
        return NULL;
    }
    while(pf->next!= NULL)
    {   
        pb  = pf->next;
        while(pb!= NULL)
        {           
            if(pf->num > pb->num)
            {
                temp = *pb;         
                *pb = *pf;
                *pf = temp;

                temp.next = pb->next;//先保存之前pb的地址
                pb->next = pf->next;
                pf->next = temp.next;//再将之前保存的pb的地址给pf->next
            }
            pb = pb ->next;     
        }
        pf = pf->next;
    }
    return (head);
}

运行结果如下:
链表排序
C文件下载:链表排序下载

用while循环和用for循环实质是一样的;

趁热打铁:看一下链表的逆序:

STU *reverse(STU *head)
 {
     STU *pf,*pb,*r;
     pf=head;
     pb=pf->next;

     while(pb!=NULL)
     {
            r=pb->next;//暂存pb的下一个节点
            pb->next=pf;//改变pf、pb之间的链接
            pf=pb;//pf、pb重新赋值,指向下一个需要改变节点位置对
            pb=r;
      }
     head->next=NULL;
     head=pf;
     return head;
}

至此:链表的创建、打印、查找、释放、删除、插入、排序、逆序代码片段全部试验完毕

实例083:约瑟夫环
循环链表实现约瑟夫环:

void Joseph(LinkList p,int m,int x){
    LinkList q;
    int i;
    if(x==0)return;
    q=p;
    m%=x;
    if(m==0)m=x;
    for(i=1;i<=m;i++){
        p=q;
        q=p->next;

    }
    p->next=q->next;
    i=q->keyword;
    printf("%d ",q->keyword);
    free(q);
    Joseph(p,i,x-1);
}

实例084:创建顺序表并插入元素:

#define Listsize 100
struct sqlist
{
    int data[Listsize];
    int length;
};
void InsertList(struct sqlist *l, int t, int i)
{
    int j;
    if (i < 0 || i > l->length)
    {
        printf("position error");
        exit(1);
    } 
    if (l->length >= Listsize)
    {
        printf("overflow");
        exit(1);
    }
    for (j = l->length - 1; j >= i; j--)
        l->data[j + 1] = l->data[j];
    l->data[i] = t;
    l->length++;
}

实例085:向链表中插入节点

实例086:从链表中删除节点

实例087:合并两个链表

实例088:单链表逆置

实例089:头插法建立单链表

           栈和队列

实例090:应用栈实现进制转换
栈的基本操作:构造一个空栈,判断栈空,判断栈满,进栈,出栈;取栈顶元素;

typedef struct{
      DataType *base;
      DataType *top;
      int stacksize;
}SeqStack;   
/* 置栈空*/
void Initial(SeqStack *s)
{/*构造一个空栈*/
      s->base=(DataType *)malloc(STACK_SIZE * sizeof(DataType));
      if(!s->base) exit (-1);
      s->top=s->base;
      s->stacksize=STACK_SIZE;

} 
/*判栈空*/
int IsEmpty(SeqStack *S)
{
    return S->top==S->base;
}
/*判栈满*/
int IsFull(SeqStack *S)
{
    return S->top-S->base==STACK_SIZE-1;
}
/*进栈*/
void Push(SeqStack *S,DataType x)
{
    if (IsFull(S))
    {
        printf("栈上溢"); /*上溢,退出运行*/
        exit(1);
    }
  *S->top++ =x;/*栈顶指针加1后将x入栈*/
}
/*出栈*/
DataType Pop(SeqStack *S)
{
    if(IsEmpty(S))
    {
        printf("栈为空"); /*下溢,退出运行*/
        exit(1);
    }
    return *--S->top;/*栈顶元素返回后将栈顶指针减1*/
}
/* 取栈顶元素*/          
DataType Top(SeqStack *S)
{                                                       
    if(IsEmpty(S))
    {
        printf("栈为空"); /*下溢,退出运行*/
        exit(1);
    }
    return *(S->top-1);
}

实例095 链队列
单链表的表首进行删除,表尾进行插入:
定义结构体:node和que

typedef struct node /*定义结点*/
{
    ElemType data;/*存放元素内容*/
    struct node *next;/*指向下个结点*/
}quenode;
struct quefr/*定义结点存放队首队尾指针*/
{
    quenode *front,*rear;
};

初始化链队列:

void creat(struct quefr *q)/*自定义函数初始化链队列*/
{
    quenode *h;
    h=(quenode *)malloc(sizeof(quenode));
    h->next=NULL;
    q->front=h;/*队首指针队尾指针均指向头结点*/
    q->rear=h;
}

元素入队列

void enqueue(struct quefr *q,int x)/*自定义函数,元素x入队*/
{
    quenode *s;
    s=(quenode *)malloc(sizeof(quenode));
    s->data=x;/*x放到结点的数据域中*/
    s->next=NULL;/*next域为空*/
    q->rear->next=s;
    q->rear=s;/*队尾指向s结点*/
}

元素出队列:

ElemType dequeue(struct quefr *q)/*自定义函数实现元素出队*/
{
        quenode *p;
        ElemType x;
    p=(quenode *)malloc(sizeof(quenode));

    if(q->front==q->rear)
    {
        printf("queue is NULL \n");
        x=0;
    }
    else
    {
        p=q->front->next;
        q->front->next=p->next;/*指向出队元素所在结点的后一个结点*/
        if(p->next==NULL)
            q->rear=q->front;
        x=p->data;
        free(p);/*释放p结点*/
    }
    return(x);
}

自定义display函数:显示队列中的元素;

void display(struct quefr dq)/*自定义函数显示队列中元素*/
{
    quenode *p;
    p=(quenode *)malloc(sizeof(quenode));
    p=dq.front->next;      /*指向第一个数据元素节点 */
    while(p!=NULL)
    {
        printf("data=%d\n",p->data);
        p=p->next;/*指向下个结点*/
    }
    printf("end \n");
}

实例096:循环缓冲区问题
循环队列:判断队空的条件:q->rear=q->front;
判断队满的条件:(q->rear+1) mod Maxsize ==q->front

队首队尾指针初始化

void init()/*队首队尾指针初始化*/
{
   front=rear=-1;
}

这地方有疑问,循环队列初始化这地方该是front=read=0,

元素的入队列操作:

int enqueue(char x)/*元素入队列*/
{
    if(front==-1&&(rear+1)==Maxsize)/*只有元素入队没有元素出队判断是否满足队满条件*/
    {
    printf("overflow!\n");
    return 0;
    }
    else if((rear+1)%Maxsize==front)/*判断是否队满*/
    {
    printf("overflow!\n");
    return 0;
    }
    else
    {
    rear=(rear+1)%Maxsize;/*rear指向下一位置*/
    queue[rear]=x;/*元素入队*/
    return 1;
    }
}

这个实例写的真另类:队列竟然直接这样定义的:
char queue[Maxsize];
int front,rear;
真是偷工减料,写的烂不想看了,能不能走点心啊;
相关代码:元素入队列,元素出队列,取队首元素,队尾元素
……
参考:
http://www.cnblogs.com/fengberlin/p/5973404.html
http://www.cnblogs.com/zhaoyl/archive/2012/09/20/2695136.html

好了 2017/07/16 11:31
下面是串与广义表:看着都是好熟悉的名字,都怪大学没学好,现在还得艰难的拾起来。
实例097:串的模式匹配
输入主串和模式串,找到子串在主串中的位置:
定义包含字符串数组和长度的结构体

#define Maxsize 100
typedef struct/*定义结构体,用来存储串的相关信息*/
{
   char string[Maxsize];
   int len;
}str;/*定义str为该结构体类型*/

匹配字符串s和字符串t,并返回匹配到字符串t在主串s中的位置

int findstr(str s,str t)/*自定义函数findstr*/
{
    int i=1,j=0,k=0,pos;
    while(i<s.len&&j<t.len)
    {
            if(s.string[k]==t.string[j])/*判断主串与模式串对应元素是否匹配*/
            {
            k++;/*主串向后移一位*/
            j++;/*模式串向后移一位*/
            }
            else
            {/*主串和模式串重新退回,从主串的下一个位置开始下一次匹配*/
                i++;
                k=i;
                j=0;
            }
    }
    if(j==t.len)/*判断模式串是否已经匹配到最后一个字符*/
        pos=k-j+1;/*指向匹配成功的第一个字符*/
    else pos=-1;
    return(pos);
}

实例099:广义表的存储:

typedef char ElemType;
typedef struct lnode
{
 int tag;
 union
 {
  ElemType data;
  struct lnode *sublist;
 }val;
 struct lnode *next;
}GLNode;

广义表的创建:creatGList(struct lnode* *gl);
广义表的打印输出:void printGList(struct lnode *gl)
求广义表的长度:int GLLength(GLNode *gl)
求广义表的深度:int GLDepth(GLNode *gl)

实例100:广义表的复制
自定义GLCcopy函数实现广义表的复制
GLNode *GLCcopy (GLNode *gl)
求广义表的表头跟表尾
2017-08-27
实例101:二叉树

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值