链表相关总结

1.单向链表倒数第k个

设置2个指针  相差k 个结点,第一个指针先走k-1步

public node  findelem(node  head   int   k){

         if(k<1){return  null;}

         node p1=head;

        node p2=head;

        for(i=0;i<k-1&&p1!=null;i++){

           p1=p1.next;

       }

      if(p1==null){return  null;}

      while(p1!=null){

       p1=p1.next;

       p2=p2.next;

    }

return  p2;

}

2.删除单向链表倒数第k个

public node  findelem(node  head   int   k){

         if(k<1){return  null;}

         node p1=head;

        node p2=head;

        for(i=0;i<k-1&&p1!=null;i++){

           p1=p1.next;

       }

      if(p1==null){return  null;}

      while(p1!=null){

       p1=p1.next;

       p2=p2.next;

    }

p2.next=p2.next.next;

return  head;

}

3.遍历一次查找出单向链表的中间结点

给两个指针 
fast slow 
让fast一次向末尾走两步,slow一次向末尾走一步,当fast指向NULL时,slow指向中间结点

public node  findelem(node  head   int   k){

         if(k<1){return  null;}

         node p1=head;

        node p2=head

      while(p1!=null&&p1.next!=null&&p1next.next!=null){

       p1=p1.next.next;

       p2=p2.next;

    }

return  p2;

}

4.用java如何判断一个单链表有循环?

【快慢指针】:需要两个指针,一个快指针:每次走两步,一个慢指针:每次走一步。 
如果快慢指针能够相遇(如果快指针能够追上慢指针),就证明有环。 
但是!!!这个方法存在问题。如果链表够长,而环又足够小,那么快指针将永远不会追上慢指针 
所以,快慢指针只适合用于环出现在链表尾部的情况,也就是单链表环的问题,而无法解决链表存在循环的问题。
 

5.链表反转

1)迭代法。先将下一节点纪录下来,然后让当前节点指向上一节点,再将当前节点纪录下来,再让下一节点变为当前节点

public Node reverse(Node node) {
    Node prev = null;
    Node now = node;
    while (now != null) {
      Node next = now.next;
      now.next = prev;
      prev = now;
      now = next;
    }

    return prev;
  }

6.为尾到头打印链表

      /*
      * 方案一:通过使用栈结构,遍历链表,把先遍历的节点的值推入栈中,遍历结束后通过弹出栈内元素实现逆序打印 
      */
      public static void printListFromTailToHeadByStack(ListNode node){
          Stack<Integer> stack=new Stack<Integer>();
         while(node!=null){
              stack.push(node.val);
             node=node.next;
         }
         while(!stack.isEmpty()){
              System.out.print(stack.pop()+",");
          }
      }
  

7.合并有序链表

  看完题目之后,思考的结果是用递归做应该是最佳的选择。但无奈水平真的有限,没有想出来如何用设计递归。提交的时候就没有用递归实现,运行结果可想而知:23ms。看了第一名的用时是5ms,而且代码简洁优美,不得不佩服

/**
*Definition for singly-linked list
*/
class ListNode{
    int val;
    ListNode next;
 
    ListNode(int x){
        val = x;
    }
}
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if (l1 == null) return l2;
        if (l2 == null) return l1;
 
        ListNode head = null;
        if (l1.val <= l2.val){
            head = l1;
            head.next = mergeTwoLists(l1.next, l2);
        } else {
            head = l2;
            head.next = mergeTwoLists(l1, l2.next);
        }
        return head;
}

8。使用两个栈实现一个队列

关于两个栈实现一个队列,栈是先进后出,队列是先进先出,所以我们考虑建立两个栈,每次将一个栈的元素出栈然后入栈第二个栈,这样再出栈第二个栈的元素就是前面先入栈的元素了,这样就可以模拟出来一个队列。但是考虑到一次队列后再入队列的时候可能导致顺序混乱,我们需要将第二个栈里的元素入栈回第一个栈才能继续入队列。

两个队列实现一个栈 
在这道题目中我们考虑到栈是先进后出的,而队列是先进先出的,因此,我们同样考虑用两个队列来模拟实现一个栈。首先将不为空的一个队列的元素出队列到另外一个队列,直到该队列只剩一个元素,然后将该元素出栈,我们就模拟实现了先进先出,也就是队列的基本形式。 
这里写图片描述

 

9.稀疏矩阵三元组存储

1. 稀疏矩阵
  一个阶数较大的矩阵中的非零元素个数s相对于矩阵元素的总个数 t 很小时,即s<ts<t,称该矩阵为稀疏矩阵。

  比如:在一个100×100的矩阵中,其中只有100个非零元素,而非零元素相对于矩阵中的元素总个数10000很小,那么该矩阵为稀疏矩阵。

2. 稀疏矩阵的压缩存储方法
  对于稀疏矩阵来说,如果我们还是用100×100的方式来存储的话,显然是非常浪费的,因此我们可以采用一种稀疏矩阵的压缩存储方式,即三元组方式。

  三元组方式存储数据的策略是只存储非零元素。但是稀疏矩阵中非零元素的分布是没有任何规律的,在这种情况下,存储方案是: 
1.存储非零元素

2.同时存储该非零元素所对应的行下标和列下标

3.稀疏矩阵中的每一个非零元素需由一个三元组(i, j, aijaij)唯一确定,稀疏矩阵中的所有非零元素构成三元组线性表,三元组中的i就是行下标,j是列下标,aijaij是对应的元素值。

 
图1-稀疏矩阵的压缩存储
  拿稀疏矩阵中的元素1来说,该元素的位置为第0行,第2列,在用三元组(i ,j ,aij)进行存储时,就是0 2 1,我们发现在这个三元组的线性表中,每个数据元素都是以三元组的方式组成的。

3. 定义存储结构
  当我们确定三元组的存储策略后,下一步我们要做的就是如何把这样的三元组存储下来。我们在进行保存时,需要把矩阵中的行数,列数,非零元素个数,矩阵中的数据都保存在data数据域(数组),在data数据域中的每个数据元素都是以三元组(行号,列号,元素值)形式存储,data域中表示的非零元素通常以行序为主序顺序排列,下标按行有序的存储结构。如图2所示: 

图2-定义存储结构
  当定义好这样的存储结构后,我们可以使用计算机程序设计语言来实现这样的存储结构,如下所示:

#define MaxSize 100

//定义三元组线性表中的数据元素存储结构
typedef struct
{
    int row;        //行号
    int col;        //列号
    ElemType d;     //元素值,ElemType为数据元素类型

} TupNode; //三元组定义


//定义三元组线性表存储结构
typedef struct
{
    int rows;                   //行数值
    int cols;                   //列数值
    int nums;                   //非零元素个数
    TupNode data[MaxSize];      //data数据域

} TSMatrix; //三元组顺序表定义

4. 稀疏矩阵的三元组基本运算
算法:以行序方式扫描二维矩阵A,将其非零的元素加入到三元组t。 
要求为data域以行序为主序顺序排列 

图中是采用6行7列的稀疏矩阵作为说明,但是在下面的算法中以3行4列说明

//以行序方式扫描二维矩阵A,将其非零的元素加入到三元组t
//以3行4列的稀疏矩阵为例
void CreatMat(TSMatrix *t, int arr[3][4])
{
    int i;
    int j;
    t->rows = 3;
    t->cols = 4;
    t->nums = 0;

    //扫描矩阵中的非零元素
    for(i = 0; i < 3; i++)
    {
        for(j = 0; j < 4; j++)
        {
            //只存非零值,以三元组方式
            if(arr[i][j] != 0)
            {
                t->data[t->nums].row = i;
                t->data[t->nums].col = j;
                t->data[t->nums].d = arr[i][j];
                t->nums++;
            }
        }
    }
}

算法:将指定位置的元素值赋给变量x:执行x=arr[ i ] [ j ]

//将三元组线性表中指定位置的元素值赋值给变量x
void arr_Assign(TSMatrix t , int *data , int i , int j)
{
    int k = 0;
    //i和j是否合法
    if(i >= t.rows || j >= t.cols)
    {
        return;
    }

    //找到指定元素的行下标
    while(k < t.nums && i > t.data[k].row)
    {
        k++;
    }

    //当找到指定元素的行下标后,再找到指定元素的列下标
    while (k < t.nums && i == t.data[k].row && j > t.data[k].col)
    {
        k++;
    }
    //如果指定元素的行和列都相等,说明找到了
    if(t.data[k].row == i && t.data[k].col)
    {
        *data = t.data[k].d;
    }
    else
    {
        //说明没找到
        *data = 0;
    }
}

算法:三元组元素赋值:执行A[ i ] [ j ] = x 
1. 将一个非0元素修改为非0值,如A[ 5 ] [ 6 ] = 8 

将矩阵A中第5行,第6列的元素的值改为8,即修改三元组线性表中的数据元素的值。 


2.将一个0元素修改为非0值,如A[ 3 ] [ 5 ] = 8 

将矩阵A中第3行,第6列值为0的元素改为8,这时需要在三元组线性表数组中下标为3的位置往后插入一个数据元素

//修改三元组元素中的值:执行A[i][j]=x
void arr_Value(TSMatrix *t , int data , int i , int j)
{
    int k = 0;
    int k1;
    //指定的行和列是否合法
    if(i >= t->rows || j >= t->cols)
    {
        return;
    }
    //先查找行
    while(k < t->nums && i > t->data[k].row)
    {
        k++;
    }

    //查找列
    while(k < t->nums && i == t->data[k].row && j > t->data[k].col)
    {
        k++;
    }

    //当找到指定位置时直接修改
    if(i == t->data[k].row && j == t->data[k].col)
    {
        t->data[k].d = data;
    }
    else
    {
        //如果指定位置不存在,则说明该元素值为0,此时插入
        for(k1 = t->nums; k1 >= k; k1--)
        {
            t->data[k1+1].col = t->data[k1].col;
            t->data[k1+1].row = t->data[k1].row;
            t->data[k1+1].d = t->data[k1].d;
        }
        //插入数据
        t->data[k].row = i;
        t->data[k].col = j;
        t->data[k].d = data;
        t->nums++;
    }
}

输出三元组:从头到尾扫描三元组t,依次输出元素值

void DispMat(TSMatrix *t)
{
    int i;
    if(t->nums <= 0)
    {
        return;
    }
    printf("\t行数:%d\t列数:%d\t元素个数:%d\n", t->rows , t->cols , t->nums);
    printf(" ------------------\n");
    //输出所有的三元组
    for(i = 0; i < t->nums; i++)
    {
        printf("\t第%d行\t第%d列\t%d\n" , t->data[i].row , t->data[i].col, t->data[i].d);
    }
}
 

10.稀疏矩阵的十字链表存储

11.哈夫曼树

哈夫曼树是一种带权路径长度最小的二叉树,也称最优二叉树。

带权路径长度是指   跟结点到各个叶结点的路径长度乘以相应结点的权值 的乘积之和称为二叉树的带权路径长度。

一、如何构建哈夫曼树?
一般可以按下面步骤构建:

将所有左,右子树都为空的作为根节点。
在森林中选出两棵根节点的权值最小的树作为一棵新树的左,右子树,且置新树的附加根节点的权值为其左,右子树上根节点的权值之和。注意,左子树的权值应小于右子树的权值。
从森林中删除这两棵树,同时把新树加入到森林中。
重复2,3步骤,直到森林中只有一棵树为止,此树便是哈夫曼树。
下面是构建哈夫曼树的图解过程:

二、哈夫曼编码
利用哈夫曼树求得的用于通信的二进制编码称为哈夫曼编码。树中从根到每个叶子节点都有一条路径,对路径上的各分支约定指向左子树的分支表示”0”码,指向右子树的分支表示“1”码,取每条路径上的“0”或“1”的序列作为各个叶子节点对应的字符编码,即是哈夫曼编码。

就拿上图例子来说: 
A,B,C,D对应的哈夫曼编码分别为:111,10,110,0

用图说明如下:

记住,设计电文总长最短的二进制前缀编码,就是以n个字符出现的频率作为权构造一棵哈夫曼树,由哈夫曼树求得的编码就是哈夫曼编码。

猜你喜欢

转载自blog.csdn.net/qq_29678299/article/details/86542124